Olive is live-development tooling for Odin. It gives you hot reload for long-running Odin programs. Change code, let Olive rebuild it, and load it into the running process without losing the program state you were testing.
All that's required is adding a tiny development-only reload/reload.odin file to
your project. During development, you start the program with olive run
instead of odin run ., and Olive uses that reload file as the dev entry
point.
You do not have to change your main proc or regular Odin build. Olive only
reads the reload adapter you point it at.
The workflow is inspired by Lisp/Clojure-style live development and Karl Zylinski's Odin Raylib hot-reload template.
Build the CLI:
odin build cmd/oliveCreate a small starter program:
./olive init scratch
cd scratch
../olive runIn another terminal:
cd scratch
../olive watchNow edit the printed text in main.odin. Olive rebuilds the changed code and
loads it into the running program. The tick counter keeps going.
olive init creates a small Odin program plus a reload directory. main.odin
has the real main, state, and app logic. reload/reload.odin is dev wiring:
it names the state type and gives Olive a small run proc to call while the
host is alive.
Treat the generated reload package as glue. Most iteration should happen in
your program files.
Olive helps when the state you want to test is annoying to recreate. A game position or a deep editor state can stay alive while you change the code around it. The same loop works for tuning visible details in a running app. See the examples below for a few workflows.
For programs whose state already lives outside the process, plain stop/build/run may still be better.
Development uses two processes:
olive runstarts the host, builds the first reloadable module, and keeps calling your reload adapter'srunproc.olive watchrebuilds the reloadable module whenever watched Odin files change.
Use olive build instead of olive watch when you want manual rebuilds.
Olive is opt-in: it only runs when you start the reload adapter through the CLI.
- Keep your existing
mainproc as the production entry point. - Put long-lived program data in one root state type.
- Add a small
reloaddirectory that wires Olive to your program. Start witholive initin a temporary directory and copy the generatedreloadshape. - In
reload/reload.odin, defineReload_State :: your_package.Program_Stateand arunproc that advances the app by one small unit of work.
Then run the host from the project root:
olive runIn another terminal, leave the watcher running:
olive watcholive run, olive build, and olive watch use reload/ by default. Pass a
reload directory only when your project uses a different location.
Olive preserves one root state value across reloads. Use that root for the state you care about keeping.
The root does not need to be one flat struct. It can own or point to smaller subsystem structs:
Program_State :: struct {
world: World_State,
renderer: ^Renderer_State,
assets: ^Asset_Cache,
}Changing proc bodies is the easy case: the host stays alive, the next build is
loaded, and state continues. Changing the layout of the root state type means
you need to restart olive run, because the host owns memory with the old
layout. Any olive watch process can stay running.
reload/reload.odin is the only file Olive reads for reload setup. Keep it
small. Import your app package, name the state type, and forward Olive's calls
to regular app procs.
package reload
import app ".."
import olive_reload "../.olive/reload/runtime/olive_reload"
Reload_State :: app.Program_State
run :: proc(state: ^Reload_State, host: ^olive_reload.Run_Host) {
_ = host
app.frame_or_tick(state)
}Required declarations:
Reload_State :: app.Program_State: the one root state type preserved by the resident host.run :: proc(state: ^Reload_State, host: ^olive_reload.Run_Host): one small unit of work. Return regularly so Olive can check for rebuilt code.
Olive writes the olive_reload runtime package under .olive/reload/runtime
when the CLI is used. The .olive/ directory is generated development output
and can be gitignored.
examples/raylib: a Raylib game loop demonstrates using some extra procs for managing the host state.examples/local_tool: an example showing composed durable state.examples/web_resource: a tiny HTTP server that watches HTML and CSS resources and broadcasts a browser refresh over SSE.
odin build cmd/olive
./olive run examples/raylib/reload./olive watch examples/raylib/reload./olive run examples/web_resource/reloadThe web example uses normal application code for the browser update. The page
opens an EventSource, and on_resource_change broadcasts a refresh event
after reloading public/index.html or public/style.css. Olive only detects
the file change and calls the hook; your app decides what to do next.
Optional lifecycle hooks are detected by name:
init :: proc(state: ^Reload_State): called for the initial load and after a forced restart. Use it to mirror production startup.on_load :: proc(state: ^Reload_State): called after a successful reload, not on the initial load.on_unload :: proc(state: ^Reload_State): called before unloading the current generation and before a forced restart resets state.on_resource_change :: proc(state: ^Reload_State, path: string): called byolive runwhen a watched non-code resource changes.force_reload :: proc(state: ^Reload_State) -> bool: return true to request a reload check even if the library timestamp did not change.force_restart :: proc(state: ^Reload_State) -> bool: return true to reset durable state with the current compatible layout.host_init :: proc(): called once in the resident host before state is created. Use this for process-owned resources.host_shutdown :: proc(): called once before the resident host exits.
Optional adapter constants:
Olive_Module_Name :: "name": basename for generated reload binaries. Pick a name that does not collide with third-party shared libraries your app loads. For example, a Raylib app should not useraylibhere on Windows, because Olive would also emitraylib.dll.Olive_Odin_Args :: "-define:FOO=true": extra args passed to generatedodin checkandodin buildcommands.Olive_Watch :: "..": comma-separated paths to poll for.odinchanges, relative to the reload directory.Olive_Watch_Resources :: "../assets,../templates": comma-separated paths to poll for non-code resource changes, relative to the reload directory.Olive_Watch_Ignore :: ".git,.olive,.worktrees": comma-separated directory names to skip while scanning watched source and resource paths. Names match exact path components. Define an empty string to scan all directories.Olive_Watch_Debounce_MS :: "150": quiet period after a detected change before rebuilding.
Initialize durable state in your app startup path and mirror that through the
adapter's init proc when needed. Use on_load for reload-only work that
depends on the new module generation.
Host hooks are for resources that should not be recreated on every reload. For example, the Raylib example uses them for its window.
Code reload and resource reload are separate. olive watch watches Odin files
and rebuilds the module. olive run can also watch external resource files and
notify the running program without rebuilding or swapping the module.
Resource watching is not just for games. It also works for web templates and stylesheets your running app knows how to reload.
Add resource paths and a hook to the adapter:
Olive_Watch_Resources :: "../assets,../templates"
on_resource_change :: proc(state: ^Reload_State, path: string) {
app.reload_resource(state, path)
}The hook receives the changed path and decides what to do. Olive only reports
the file change. The web resource example reloads its own files and uses SSE to
notify the browser. Olive ignores .odin files in resource watches; source
files should go through olive watch.
For browser or UI clients connected to a running process, on_load can push a
fresh snapshot after a reload:
on_load :: proc(state: ^Reload_State) {
app.broadcast_snapshot_to_connected_clients(state)
}Keep client connection state and current application data in durable state or
host-owned state. Avoid storing callbacks or function pointers from reloadable
code in those long-lived clients; after a reload, those pointers can refer to
old code. Let the new generation's on_load serialize or render the current
state and push it again.
Olive also has experimental scratch eval helpers. The idea is to get some of
the feel of a REPL workflow in Odin: write a small expression next to the code
you are thinking about, run it from the editor, see the result, and keep moving
without making a temporary main.
This is not a persistent Odin REPL. Each eval writes a small Odin runner and
calls odin. The useful part is the workflow. Selected expressions and small
comment-block scratchpads run in package context. Saved outputs can stand in
for the bits of state you would keep around in a REPL session.
Try a call next to the code it exercises.
add :: proc(a, b: int) -> int {
return a + b
}
// add(5, 2) <cursor>With the cursor on the comment line, an editor command can evaluate just
add(5, 2) in the package context and show the result. Multi-line comment
blocks work the same way:
/*
first := add(5, 2)
second := add(first, 10)
second
*/ <cursor>Scratch eval can also save successful eval output under a name. Olive stores
these values under the package's .olive/values directory by default, or under
OLIVE_STORE_DIR if that environment variable is set.
You can also run eval from the CLI:
./olive eval /path/to/package 'target.some_proc()'
./olive eval /path/to/package 'target.some_proc()' --save latestThe Emacs integration lives in emacs/olive.el.
(add-to-list 'load-path "<path-to>/olive/emacs")
(require 'olive)
(add-hook 'odin-mode-hook #'olive-setup-odin-mode-keys)Build ./olive first, or customize olive-command.
Pull requests, issues, and feedback are welcome. Bug reports and notes from trying Olive in real Odin projects are useful.
Olive is licensed under the MIT License. See LICENSE.
Olive's hot-reload workflow is inspired in part by Karl Zylinski's Odin Raylib hot reload template:
https://github.com/karl-zylinski/odin-raylib-hot-reload-game-template
The broader motivation comes from Clojure and Lisp development. Keep the program alive, evaluate small pieces of code, and avoid restarting the whole system all the time.




