diff --git a/doc/source/reference/utils.rst b/doc/source/reference/utils.rst index 87efc1fdca..9d96e7bc80 100644 --- a/doc/source/reference/utils.rst +++ b/doc/source/reference/utils.rst @@ -5,12 +5,13 @@ ========== This section documents the command-line tools that ship with daslang: -the test framework, the code coverage tool, the package manager, and -the MCP server for AI coding assistants. +the live-reload application host, the test framework, the code coverage +tool, the package manager, and the MCP server for AI coding assistants. .. toctree:: :maxdepth: 2 + utils/daslang_live.rst utils/dastest.rst utils/dascov.rst utils/daspkg.rst diff --git a/doc/source/reference/utils/daslang_live.rst b/doc/source/reference/utils/daslang_live.rst new file mode 100644 index 0000000000..6085085b20 --- /dev/null +++ b/doc/source/reference/utils/daslang_live.rst @@ -0,0 +1,561 @@ +.. _utils_daslang_live: + +.. index:: + single: Utils; daslang-live + single: Utils; Live Reload + single: Utils; Hot Reload + +============================================== + daslang-live --- Live-Reload Application Host +============================================== + +``daslang-live`` is a live-reloading application host for daslang. +Edit a ``.das`` file, save it, and the running application picks up +the changes instantly --- preserving windows, GPU state, game entities, +and everything stored in the persistent byte store. No restart, no +lost state. Applications range from games to REST APIs to MCP plugins. + +.. contents:: + :local: + :depth: 2 + + +Designing for live reload +========================= + +A live-reload host can recompile your script and swap in the new code, +but it cannot know *what matters* to your application. A GLFW window +handle, a network socket, a game score, an audio buffer --- these are +application-specific. The host provides the machinery; the application +must cooperate. + +Five requirements every live-reloadable application must handle: + +1. **Full restart capability.** + ``init()`` and ``shutdown()`` must be idempotent. The host may + cold-start the application at any time --- after a crash, after a + failed reload, or on first launch. + +2. **Compilation failure recovery.** + A typo should not kill a running application. The host reverts to + the old code and pauses, but the application must tolerate being + frozen mid-frame until the next successful reload. + +3. **Runtime exception handling.** + A crash in ``update()`` should not corrupt persistent state. The + host clears the store on exception, so the application must handle + starting from scratch gracefully. + +4. **State persistence.** + GPU resources (windows, buffers, shaders) and game state (entities, + scores) live outside the script context. The application must + explicitly save and restore what matters across reloads. The + ``@live`` macro and ``decs_live`` module automate the common cases, + but the developer decides what survives and what starts fresh. + +5. **Instance management.** + Only one instance should run. The host enforces this via a system + mutex, but the application's external resources (ports, files) must + also be safe for single-instance operation. + +This is by design: explicit control over persistence is safer than +magical state preservation that silently carries stale data. + + +Quick start +=========== + +Run the hello example:: + + bin/Release/daslang-live.exe examples/daslive/hello/main.das + +A GLFW window opens with a colored background. Edit ``main.das``, +save --- the window stays open and the new code takes effect. + +Here is the full ``hello/main.das``: + +.. code-block:: das + + options gen2 + + require live/glfw_live + require opengl/opengl_boost + require live/live_commands + require live/live_api + require daslib/json + require daslib/json_boost + require live_host + + // --- State --- + + var bg_r = 0.2f + var bg_g = 0.3f + var bg_b = 0.5f + var frame_count : int = 0 + + // --- Live commands --- + + [live_command] + def set_color(input : JsonValue?) : JsonValue? { + if (input != null && input.value is _object) { + let tab & = unsafe(input.value as _object) + var rv = tab?["r"] ?? null + if (rv != null && rv.value is _number) { + bg_r = float(rv.value as _number) + } + var gv = tab?["g"] ?? null + if (gv != null && gv.value is _number) { + bg_g = float(gv.value as _number) + } + var bv = tab?["b"] ?? null + if (bv != null && bv.value is _number) { + bg_b = float(bv.value as _number) + } + } + return JV("\{\"r\": {bg_r}, \"g\": {bg_g}, \"b\": {bg_b}}") + } + + // --- Lifecycle --- + + [export] + def init() { + live_create_window("Hello daslive", 640, 480) + print("hello: init (is_reload={is_reload()})\n") + if (!is_reload()) { + frame_count = 0 + } + } + + [export] + def update() { + if (!live_begin_frame()) { + return + } + frame_count++ + if (frame_count % 300 == 0) { + print("hello: frame {frame_count}, dt={get_dt()}, uptime={get_uptime()}\n") + } + var w, h : int + live_get_framebuffer_size(w, h) + glViewport(0, 0, w, h) + glClearColor(bg_r, bg_g, bg_b, 1.0) + glClear(GL_COLOR_BUFFER_BIT) + live_end_frame() + } + + [export] + def shutdown() { + print("hello: shutdown (frames={frame_count})\n") + live_destroy_window() + } + + // Dual-mode: also works with regular daslang.exe + [export] + def main() { + init() + while (!exit_requested()) { + update() + } + shutdown() + } + +The same script works under both ``daslang-live.exe`` (live reload) +and ``daslang.exe`` (standalone). Under ``daslang.exe`` the ``main()`` +function drives the loop; under ``daslang-live.exe`` the host calls +``init()``, ``update()``, and ``shutdown()`` directly. + + +Mode detection +============== + +The host inspects the script's exported functions to choose a mode: + +- **Lifecycle mode** --- the script exports ``init()``. The host calls + ``init()``, loops ``update()``, and calls ``shutdown()`` on exit. + This is the primary live-reload mode. + +- **Simple mode** --- the script only exports ``main()``. The host + calls ``main()`` directly, behaving identically to ``daslang.exe``. + + +Lifecycle +========= + +**Normal startup:** +``init()`` → ``update()`` loop → ``shutdown()`` on exit. + +**Reload cycle** (triggered by file change or ``POST /reload``): + +1. ``[before_reload]`` functions are called (save state). +2. ``shutdown()`` is called. +3. The host recompiles the script. +4. A new context is created. +5. ``[after_reload]`` functions are called (restore state). +6. ``init()`` is called in the new context. + +**Failed reload:** +The host reverts to the old context, pauses execution, and stores the +compilation error. Retrieve it via ``GET /error`` or +``get_last_error()``. The next successful reload unpauses +automatically. + +**Runtime exception:** +The host pauses, clears the persistent store (potentially corrupted), +and sets the error. + + +Core API +======== + +``require live_host`` + +Lifecycle and timing +-------------------- + +.. list-table:: + :header-rows: 1 + :widths: 40 60 + + * - Function + - Description + * - ``is_live_mode() : bool`` + - True when running under ``daslang-live.exe``. + * - ``is_reload() : bool`` + - True during the first frame after a reload. + * - ``request_exit()`` + - Signal the host to exit after the current frame. + * - ``exit_requested() : bool`` + - True after ``request_exit()`` or window close. + * - ``request_reload(full : bool)`` + - Request a reload. ``full=true`` also clears ``@live`` vars. + * - ``get_dt() : float`` + - Frame delta time in seconds. + * - ``get_uptime() : float`` + - Time since process start. Does **not** reset on reload. + * - ``get_fps() : float`` + - Current frames per second. + * - ``is_paused() : bool`` + - True when execution is paused. + * - ``set_paused(v : bool)`` + - Pause or unpause execution. + +Persistent store +---------------- + +.. list-table:: + :header-rows: 1 + :widths: 40 60 + + * - Function + - Description + * - ``live_store_bytes(key, data)`` + - Store a byte array under a string key. Survives reloads. + * - ``live_load_bytes(key, data) : bool`` + - Load a byte array. Returns ``false`` if the key is not found. + * - ``get_last_error() : string`` + - Last compilation error (empty string if none). + + +Reload annotations +================== + +.. list-table:: + :header-rows: 1 + :widths: 25 75 + + * - Annotation + - Description + * - ``[before_reload]`` + - Called before ``shutdown()`` during a reload. Save state here. + * - ``[after_reload]`` + - Called after recompile, before ``init()``. Restore state here. + * - ``[before_update]`` + - Called every frame before ``update()``. Used internally by + ``live_api``. + +The host discovers annotated functions by name prefix +(``__before_reload_*``, ``__after_reload_*``, ``__before_update_*``). +Multiple functions with the same annotation are all called. + + +State persistence +================= + +``@live`` variables (recommended) +--------------------------------- + +``require live/live_vars`` + +Tag globals with ``@live`` and the macro auto-generates +``[before_reload]``/``[after_reload]`` handlers that serialize them +via ``Archive``. No manual save/restore needed: + +.. code-block:: das + + options gen2 + + require live/live_vars + require live_host + + var @live counter = 0 + var @live name = "world" + var @live values : array + + [export] + def init() { + if (!is_reload()) { + counter = 0 + name = "world" + values |> clear() + values |> push(1.0) + values |> push(2.0) + values |> push(3.0) + print("init: first run\n") + } else { + print("init: reload - counter={counter}, name={name}\n") + } + } + +A hash of each variable's init expression is stored alongside the +data. If you change the default value in code, old stored data is +discarded and the new default takes effect --- safe format migration. + +Works with POD types, strings, enums, arrays, tables, and structs +with serializable fields. + +Full reload (``request_reload(true)`` or ``POST /reload/full``) clears +all ``@live`` entries. + +Manual serialization +-------------------- + +For complex cases (handles, non-serializable types), use +``[before_reload]``/``[after_reload]`` with ``live_store_bytes``/ +``live_load_bytes`` and the ``Archive`` module. See +``examples/daslive/reload_test/`` for the full pattern. + + +Helper modules +============== + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Module + - Description + * - ``live/glfw_live`` + - GLFW window that persists across reloads. + * - ``live/opengl_live`` + - OpenGL screenshot command. + * - ``live/decs_live`` + - Auto-serialization of DECS entities across reloads. + * - ``live/live_commands`` + - ``[live_command]`` annotation for REST-callable functions. + * - ``live/live_vars`` + - ``@live`` variable macro (auto-persistence). + * - ``live/live_watch`` + - File watcher (auto-reload on save). + * - ``live/live_watch_boost`` + - File watcher with diagnostic commands (recommended over + ``live_watch``). + * - ``live/live_api`` + - REST API server on port 9090. + * - ``live/audio_live`` + - Audio state persistence across reloads. + +``live/glfw_live`` +------------------ + +The GLFW window handle is stored in the persistent byte store and +survives reloads. Key functions: + +.. list-table:: + :header-rows: 1 + :widths: 40 60 + + * - Function + - Description + * - ``live_create_window(title, w, h)`` + - Create or retrieve the persistent GLFW window. + * - ``live_destroy_window()`` + - Destroy the window (skipped during reload). + * - ``live_begin_frame() : bool`` + - Poll events, return ``false`` if window should close. + * - ``live_end_frame()`` + - Swap buffers. + * - ``live_get_framebuffer_size(w, h)`` + - Query framebuffer dimensions. + +``live/decs_live`` +------------------ + +Require this module and all DECS entities auto-persist across reloads. +Guard ``decs::restart()`` with ``is_reload()`` to avoid wiping +restored entities: + +.. code-block:: das + + if (!is_reload()) { + decs::restart() // only on first run, not after reload + } + +``live/live_commands`` +---------------------- + +Functions annotated ``[live_command]`` are callable via +``POST /command``. Signature: +``def cmd_name(input : JsonValue?) : JsonValue?``. +Convention: prefix with ``cmd_``. The ``set_color`` command in the +hello example above demonstrates the pattern. + + +REST API +======== + +``require live/live_api`` starts an HTTP server on port 9090. +Configure the port with ``live_api_set_port()`` before ``init()``. + +Endpoints +--------- + +.. list-table:: + :header-rows: 1 + :widths: 10 20 70 + + * - Method + - Path + - Description + * - GET + - ``/status`` + - JSON: ``fps``, ``uptime``, ``paused``, ``dt``, ``has_error``. + * - GET + - ``/error`` + - Plain text: last compilation error. + * - POST + - ``/reload`` + - Incremental reload. + * - POST + - ``/reload/full`` + - Full recompile (clears ``@live`` vars). + * - POST + - ``/pause`` + - Pause execution. Returns 503 if compile error active. + * - POST + - ``/unpause`` + - Resume execution. + * - POST + - ``/shutdown`` + - Graceful shutdown. + * - POST + - ``/command`` + - Dispatch a ``[live_command]`` via JSON body: + ``{"name":"cmd_name","args":{...}}``. + * - ANY + - ``*`` + - JSON help with all endpoints and curl examples. + +curl examples +------------- + +Check status:: + + curl http://localhost:9090/status + +Trigger a reload:: + + curl -X POST http://localhost:9090/reload + +Call a live command:: + + curl -X POST http://localhost:9090/command \ + -d '{"name":"set_color","args":{"r":1.0,"g":0.0,"b":0.0}}' + +When using the daslang MCP server, prefer ``live_*`` MCP tools over +curl (see :ref:`utils_mcp`). + + +CLI reference +============= + +:: + + daslang-live.exe [options] script.das [-- script-args...] + +.. list-table:: + :header-rows: 1 + :widths: 25 75 + + * - Flag + - Description + * - ``-project `` + - Project file (``.das_project``). + * - ``-dasroot `` + - Override ``DAS_ROOT``. + * - ``-cwd`` + - Change to the script's directory before loading. + * - ``--`` + - Separator: everything after is passed to the script. + + +Examples +======== + +.. list-table:: + :header-rows: 1 + :widths: 35 65 + + * - Path + - Description + * - ``examples/daslive/hello/`` + - Minimal GLFW window with background color tuning. + * - ``examples/daslive/triangle/`` + - DECS + OpenGL shaders, rotating triangle. + * - ``examples/daslive/arcanoid/`` + - Full breakout game: DECS, audio, particles, 30+ live commands. + * - ``examples/daslive/sequence/`` + - Card board game: multi-module, bot AI, tournament runner. + * - ``examples/daslive/tank_game/`` + - 3D tank combat with dynamic lighting. + * - ``examples/daslive/live_vars_demo/`` + - ``@live`` variable persistence demo. + * - ``examples/daslive/reload_test/`` + - Manual state persistence with ``[before_reload]``/``[after_reload]``. + * - ``examples/daslive/test_commands/`` + - ``[live_command]`` registration and dispatch. + * - ``examples/daslive/test_api/`` + - JSON-based live commands via REST. + * - ``examples/daslive/test_api_http/`` + - Full HTTP REST API integration test. + * - ``examples/daslive/test_decs_reload/`` + - DECS entity persistence verification. + * - ``examples/daslive/test_watch/`` + - File watcher integration test. + + +Tips and gotchas +================ + +- ``.das_module`` changes require restarting ``daslang-live.exe`` + (module paths are registered at startup only). +- ``get_uptime()`` does **not** reset on reload --- use + ``is_reload()`` to detect reloads. +- Debug agents persist across reloads; their code is **not** updated + on reload (restart required to pick up agent code changes). +- ``[live_command]`` functions cannot be defined in the same module + that registers them --- use a separate module. +- Failed reload pauses the host --- check ``GET /error`` or + ``get_last_error()``. +- A single-instance lock prevents running two ``daslang-live.exe`` + processes on the same script. +- Guard ``decs::restart()`` with ``if (!is_reload())`` to avoid + wiping restored entities. + + +.. seealso:: + + ``examples/daslive/`` -- live-reload example applications + + `Running It Live `_ -- blog post on live-coding philosophy + + :ref:`utils_mcp` -- MCP server with live-reload control tools (``live_*`` tools) diff --git a/doc/source/reference/utils/mcp.rst b/doc/source/reference/utils/mcp.rst index 4100051214..fe0f22dd2e 100644 --- a/doc/source/reference/utils/mcp.rst +++ b/doc/source/reference/utils/mcp.rst @@ -9,7 +9,7 @@ MCP Server --- AI Tool Integration =========================================== -The daslang MCP server exposes 20 compiler-backed tools to AI coding +The daslang MCP server exposes 28 compiler-backed tools to AI coding assistants via the `Model Context Protocol `_. It provides compilation diagnostics, type inspection, go-to-definition, find-references, AST dump, AOT generation, expression evaluation, @@ -57,7 +57,7 @@ session. Tools ===== -The server exposes 20 tools. Each tool is invoked via MCP's +The server exposes 28 tools. Each tool is invoked via MCP's ``tools/call`` method. Compilation and diagnostics @@ -199,6 +199,37 @@ Parse-aware search (tree-sitter) tree-sitter. Works on broken/incomplete code -- no compilation needed. Conditional on ``sg`` CLI. +Live-reload control +------------------- + +These tools interact with a running :ref:`daslang-live ` +instance via its REST API. All accept an optional ``port`` parameter +(default 9090). + +.. list-table:: + :header-rows: 1 + :widths: 25 75 + + * - Tool + - Description + * - ``live_launch`` + - Launch ``daslang-live.exe`` on a script if not already running. + Sets working directory to the script's folder. + * - ``live_status`` + - Query the running instance for fps, uptime, paused state, and + error status. + * - ``live_error`` + - Retrieve the last compilation error (plain text). + * - ``live_reload`` + - Trigger an incremental or full reload. + * - ``live_pause`` + - Pause or unpause execution. + * - ``live_command`` + - Dispatch a ``[live_command]`` by name with JSON arguments. + * - ``live_shutdown`` + - Gracefully shut down the running instance. + + ast-grep / tree-sitter setup ============================= @@ -290,3 +321,5 @@ Optionally, allow all MCP tools without prompting by adding to ``utils/mcp/README.md`` -- setup and configuration details `Model Context Protocol specification `_ + + :ref:`utils_daslang_live` -- the live-reload application host controlled by ``live_*`` tools