daemon-agent
DaemonAgent — dual-language game project (C++20 + TypeScript)
daemon-agent is a C++20 “cpp-game” project that embeds a V8-based JavaScript game framework for game logic and rapid hot-reload. The daemon-agent separates deterministic rendering/engine work (main thread) from dynamic game logic (worker thread running V8). The architecture emphasizes fault isolation, hot-reloadability, and a dual-language runtime where core engine subsystems are C++ and gameplay/AI systems are authored in ES6 modules.
High-level goals:
- Keep main render loop stable at 60 FPS while running JavaScript game logic on a dedicated worker thread (JSGameLogicJob).
- Provide a scriptable bridge (GameScriptInterface, KADIScriptInterface, CommandQueue) so JS can request engine services and be hot-reloaded.
- Enable development tooling (file watching, inject input, script file ops) via script-callable methods.
Tech stack:
- C++20 engine (MSBuild / Visual Studio oriented)
- V8 JavaScript as embedded runtime for game logic (V8 isolates per worker)
- FMOD audio optional (controlled through EngineBuildPreferences.hpp)
- ES6 module-based JavaScript game framework under Run/Data/Scripts
Example of engine-level build preferences (Code/Game/EngineBuildPreferences.hpp):
#pragma once#define ENGINE_DEBUG_RENDER#define CONSOLE_HANDLER#define ENGINE_SCRIPTING_ENABLED // Phase 2: Enable async audio via AudioCommandQueue// #define ENGINE_DISABLE_AUDIO // (If uncommented) Disables AudioSystem code and fmod linkage.Module Architecture
Section titled “Module Architecture”daemon-agent is split into C++ engine subsystems and a JS framework that runs inside V8. Key runtime modules and the primary data/control flows:
-
C++ Engine (Code/Game/Framework + Engine/*)
- App (Code/Game/Framework/App.hpp): owns lifecycle (Startup, Shutdown, RunMainLoop), orchestrates frame phases and wiring between engine subsystems and script subsystem.
- Game (Code/Game/Gameplay/Game.hpp): concrete game logic façade implementing IJSGameLogicContext for worker-thread JavaScript callbacks.
- JSGameLogicJob (Code/Game/Framework/JSGameLogicJob.hpp): dedicated worker thread managing V8 isolate, executing game logic each frame and synchronizing with main thread via condition variables and mutexes.
- GameScriptInterface (Code/Game/Framework/GameScriptInterface.hpp): exposes script-callable methods/properties to V8 (file ops, input injection, quit requests, JS eval/load).
- GenericCommandQueue / CallbackQueue / Command pattern: asynchronous cross-thread command submission between JS and engine for audio, rendering commands, file-watcher events.
-
JavaScript Framework (Run/Data/Scripts)
- JSEngine.js: Core subsystem registration, system lifecycle and hot-reload hooks.
- main.js: JavaScript entry point (imported by C++ into V8).
- JSGame.js and component systems (InputSystem, AudioSystem, PhysicsSystem, etc.): implement gameplay systems in JS and register with JSEngine.
Data flow (per-frame):
- App::RunFrame / RunMainLoop triggers BeginFrame and signals worker to run JS for the upcoming frame.
- JSGameLogicJob wakes, acquires v8::Locker, executes registered JS systems (via JSEngine/JSGame), writes to EntityStateBuffer and/or submits render commands.
- Worker signals completion (condition_variable). Main thread swaps entity buffers, consumes render commands, and renders stable frame.
- Script hot-reload and file-watcher events are processed via script-callable GameScriptInterface methods and C++ FileWatcher hooks.
Synchronization model:
- Worker thread (JSGameLogicJob) runs V8 calls protected by v8::Locker.
- Main thread uses mutex/condition_variable to wake & wait for worker.
- Shared state flows through double-buffered EntityStateBuffer (Engine/Entity/EntityStateBuffer.hpp referenced).
Key Modules / Subsystems and Responsibilities
Section titled “Key Modules / Subsystems and Responsibilities”- App (Code/Game/Framework/App.hpp)
- Lifecycle management: Startup, Shutdown, RunFrame, RunMainLoop
- Wiring scripting bindings and processing generic command queues
- Cursor and input mode updates
- Game (Code/Game/Gameplay/Game.hpp)
- Implements IJSGameLogicContext for worker thread callbacks
- Provides methods to execute JS code (ExecuteJavaScriptCommand/File/Module)
- Tracks JS exception counts and demo window state
- JSGameLogicJob (Code/Game/Framework/JSGameLogicJob.hpp)
- Continuous worker thread model for V8 isolate lifetime
- Condition-variable synchronization with main thread
- Error isolation and graceful shutdown semantics
- GameScriptInterface (Code/Game/Framework/GameScriptInterface.hpp)
- Script-visible API: Quit, execute JS, file ops, input injection, file-watcher management
- JSEngine (Run/Data/Scripts/JSEngine.js)
- System registration, priority scheduling, hot-reload and global EventBus
- JS Systems (Run/Data/Scripts/*)
- Implement gameplay components (InputSystem, AudioSystem, PhysicsSystem, etc.)
- Register with JSEngine for update/render phases
Key Classes
Section titled “Key Classes”Provide the most relevant classes with their roles (as defined in source headers):
- App
- Location: Code/Game/Framework/App.hpp
- Role: Main application orchestrator. Initializes scripting bindings, handles frame lifecycle and processes generic command queues.
- Snippet:
class App{public: App(); ~App();
void Startup(); void Shutdown(); void RunFrame();
void RunMainLoop();
static bool OnCloseButtonClicked(EventArgs& args); static void RequestQuit(); static bool m_isQuitting;
private: void BeginFrame() const; void Update(); void Render() const; void EndFrame() const;
static std::any OnPrint(std::vector<std::any> const& args); static std::any OnDebug(std::vector<std::any> const& args); static std::any OnGarbageCollection(std::vector<std::any> const& args);
void UpdateCursorMode(); void SetupScriptingBindings();
// Command Processing void ProcessGenericCommands();
// Rendering};- Game
- Location: Code/Game/Gameplay/Game.hpp
- Role: Implements IJSGameLogicContext; bridges worker-thread JS callbacks with engine services, provides JS execution entry points from C++ and script-exposed control.
- Snippet:
class Game : public IJSGameLogicContext{public: Game(); ~Game();
void PostInit(); void UpdateJS(); void RenderJS(); void ShowSimpleDemoWindow();
bool IsAttractMode() const;
// JavaScript execution void ExecuteJavaScriptCommand(String const& command); void ExecuteJavaScriptFile(String const& filename); void ExecuteModuleFile(String const& modulePath);
// IJSGameLogicContext interface (worker thread) void UpdateJSWorkerThread(float deltaTime) override; void RenderJSWorkerThread(float deltaTime) override; void HandleJSException(char const* errorMessage, char const* stackTrace) override;
// JavaScript error monitoring uint64_t GetJSExceptionCount() const { return m_jsExceptionCount.load(std::memory_order_relaxed); } bool HasJSExceptions() const { return m_jsExceptionCount.load(std::memory_order_relaxed) > 0; } void ResetJSExceptionCount() { m_jsExceptionCount.store(0, std::memory_order_relaxed); }
private: void InitializeJavaScriptFramework(); bool IsScriptSubsystemReady() const; bool IsJSEngineReady(char const* methodName) const;
bool m_showDemoWindow = true; std::atomic<uint64_t> m_jsExceptionCount{0};};- JSGameLogicJob
- Location: Code/Game/Framework/JSGameLogicJob.hpp
- Role: Worker-thread job that maintains a V8 isolate, executes JS game logic per-frame, and provides synchronization to main thread. Contains mutex/condition_variable and uses v8::Locker/TryCatch.
- Snippet (header top & design notes extracted):
// Phase 1: Async Architecture - JavaScript Worker Thread Job//// Purpose:// Executes JavaScript game logic on a dedicated worker thread, isolated from main render thread.// Enables fault-tolerant async architecture where JavaScript failures don't impact 60 FPS rendering.#pragma once
#include "Engine/Core/JobSystem.hpp"#include "Engine/Entity/EntityStateBuffer.hpp"
#include <atomic>#include <condition_variable>#include <mutex>
// Forward Declarationsclass IJSGameLogicContext; // Abstract interface for JavaScript execution contextclass CallbackQueue; // Phase 2.3: Lock-free callback queue for async callback processing
namespace v8 {class Isolate;class TryCatch;template<class T> class Local;class Context;}- GameScriptInterface
- Location: Code/Game/Framework/GameScriptInterface.hpp
- Role: Exposes engine functionality to scripts. Supports method registry, calling JS methods to perform engine tasks (file ops, inject input, file-watcher management).
- Snippet:
class GameScriptInterface : public IScriptableObject{public: explicit GameScriptInterface(Game* game);
std::vector<ScriptMethodInfo> GetAvailableMethods() const override; StringList GetAvailableProperties() const override;
ScriptMethodResult CallMethod(String const& methodName, ScriptArgs const& args) override; std::any GetProperty(String const& propertyName) const override; bool SetProperty(String const& propertyName, std::any const& value) override;
private: Game* m_game;
void InitializeMethodRegistry() override;
ScriptMethodResult ExecuteAppRequestQuit(ScriptArgs const& args); ScriptMethodResult ExecuteJavaScriptCommand(ScriptArgs const& args); ScriptMethodResult ExecuteJavaScriptFile(ScriptArgs const& args);
// Phase 6a: KADI Development Tools - File Operations ScriptMethodResult ExecuteCreateScriptFile(ScriptArgs const& args); ScriptMethodResult ExecuteReadScriptFile(ScriptArgs const& args); ScriptMethodResult ExecuteDeleteScriptFile(ScriptArgs const& args);
// Phase 6a: KADI Development Tools - Input Injection ScriptMethodResult ExecuteInjectKeyPress(ScriptArgs const& args); ScriptMethodResult ExecuteInjectKeyHold(ScriptArgs const& args); ScriptMethodResult ExecuteKeyHoldSequence(const std::string& keySequenceJson); ScriptMethodResult ExecuteGetKeyHoldStatus(ScriptArgs const& args); ScriptMethodResult ExecuteCancelKeyHold(ScriptArgs const& args); ScriptMethodResult ExecuteListActiveKeyHolds(ScriptArgs const& args);
// Phase 6b: KADI Development Tools - FileWatcher Management ScriptMethodResult ExecuteAddWatchedFile(ScriptArgs const& args); ScriptMethodResult ExecuteRemoveWatchedFile(ScriptArgs const& args); ScriptMethodResult ExecuteGetWatchedFiles(ScriptArgs const& args);};V8 Scripting API
Section titled “V8 Scripting API”The JS framework is an ES6 module-based set of scripts loaded into a V8 context managed by JSGameLogicJob and bridged to the engine via script interfaces.
JS entry points and framework (selected snippets):
- main.js — JavaScript framework entry point loaded by C++:
// main.js - JavaScript Framework Entry Pointimport './InputSystemCommon.js';import { JSEngine } from './JSEngine.js';import { JSGame } from './JSGame.js';import { CommandQueue } from './Interface/CommandQueue.js'; // GenericCommand facade
console.log('(main.js)(start) - JavaScript Framework Entry Point');
// CommandQueue MUST be created before JSGame (API methods depend on globalThis.CommandQueueAPI)const commandQueueInstance = new CommandQueue();globalThis.CommandQueueAP...(truncated)- JSEngine.js — core JS engine API exposed to game systems:
// JSEngine.js - Core JavaScript Engine Frameworkexport class JSEngine { constructor() { this.game = null; this.isInitialized = true; this.frameCount = 0; this.registeredSystems = new Map(); this.updateSystems = []; this.renderSystems = []; this.pendingOperations = []; this.hotReloadEnabled = true; this.eventBus = new EventBus(); console.log('JSEngine: EventBus created (global event system)'); globalThis.eventBus = this.eventBus; console.log('JSEngine: Created with system registration support'); }
setGame(gameInstance) { this.game = gameInstance; console.log('JSEngine: Game instance set'); }
registerSystem(id, configOrComponent = {}) { let system; const isComponentInstance = configOrComponent && typeof configOrComponent === 'object' && configOrComponent.id && typeof configOrComponent.priority === 'number' && typeof configOrComponent.update === 'function'; ... }}How C++ interacts with JS:
- The C++ Game class calls into V8 via an IJSGameLogicContext implementation; JS code registers systems on startup. C++ triggers per-frame execution by signaling the JS worker to run and later swaps entity state buffers to consume JS-produced state.
- GameScriptInterface provides methods callable from scripts that request engine services (file operations, input injection, quit, execute JS files/commands). These are registered into the V8 global context during App::SetupScriptingBindings.
Example JS usage patterns:
- Registering a system (from JS):
const engine = new JSEngine();engine.registerSystem('InputSystem', { update: (dt) => { /* read inputs and emit events */ }, render: (dt) => { /* debug render */ }, priority: 10});- Using engine-provided constants (InputSystemCommon.js):
import { KEYCODE_SPACE, KEYCODE_F1 } from './InputSystemCommon.js';if (input.isKeyPressed(KEYCODE_SPACE)) { // ...}Error handling and isolation:
- JSGameLogicJob wraps V8 calls with v8::TryCatch and isolates JS exceptions. Game::HandleJSException is invoked on worker thread to record/handle exceptions and prevent process-wide crashes.
Build & Configuration
Section titled “Build & Configuration”- Language & Standard: C++20 (project defines and headers assume C++20 features and std threading).
- Build system: MSBuild / Visual Studio projects are expected (project layout and naming follow a typical Windows engine workflow). The codebase references Engine/* modules and Game/* modules included in the solution.
- Dependencies:
- V8 — embedded JavaScript engine (per JSGameLogicJob forward declarations expect v8::Isolate, v8::Context, v8::Locker).
- FMOD (optional) — audio subsystem can be disabled via EngineBuildPreferences.hpp: ENGINE_DISABLE_AUDIO toggles FMOD linkage and audio code paths.
- Standard C++ libraries: threading (std::thread, std::mutex, std::condition_variable), atomics,
, STL containers.
- Platform requirements: Host platform must support embedded V8 and the C++ toolchain for building V8-linked C++ binaries. Typically Windows with Visual Studio for the default build configurations; cross-platform builds require V8 port and adjustments (not shown in source).
- Configuration points:
- Code/Game/EngineBuildPreferences.hpp controls per-game engine compilation flags (ENGINE_DEBUG_RENDER, CONSOLE_HANDLER, ENGINE_SCRIPTING_ENABLED).
- Script hot-reload is controlled at C++ level by FileWatcher and ScriptReloader components (file watcher APIs are exposed to scripts via GameScriptInterface).
Build notes:
- Ensure V8 headers and libs are available to the MSBuild project and linked into the executable that hosts JSGameLogicJob.
- If FMOD is not required, define ENGINE_DISABLE_AUDIO in EngineBuildPreferences.hpp (or leave commented to link FMOD).
- The script runtime expects ES6 module support on the V8 embedding side — the engine’s script loader should handle module resolution from Run/Data/Scripts.
Integration with AGENTS (KĀDI broker/platform)
Section titled “Integration with AGENTS (KĀDI broker/platform)”daemon-agent integrates with the AGENTS/KĀDI platform via scriptable broker interfaces and generic command queues:
- C++ side provides KADIScriptInterface and KADIScript-related forward declarations (Code/Game/Framework/App.hpp forward decls include KADIScriptInterface). These components mediate communication between the local agent and the KĀDI broker (tooling, telemetry, remote script control).
- GameScriptInterface includes methods targeted at KADI development tools (file operations, input injection, file-watcher management), enabling KĀDI to:
- Hot-reload scripts and request script runs (ExecuteJavaScriptFile / ExecuteJavaScriptCommand).
- Manipulate local files used by scripts for development and testing (ExecuteCreateScriptFile / ExecuteReadScriptFile / ExecuteDeleteScriptFile).
- Inject input events remotely for automated testing (ExecuteInjectKeyPress / ExecuteInjectKeyHold / ExecuteKeyHoldSequence).
- Configure file watchers to enable hot-reload notifications back to KADI (ExecuteAddWatchedFile / ExecuteRemoveWatchedFile / ExecuteGetWatchedFiles).
Connection surface summary:
- GenericCommandQueue & GenericCommandScriptInterface (forward-declared in App.hpp) enable cross-language command submission and remote control messages to be enqueued and processed by the engine.
- KADIScriptInterface likely registers script-callable endpoints to receive broker messages and translate them into local GameScriptInterface operations.
- The JavaScript JSEngine provides a runtime-level API (globalThis.command queue/API) so agent-supplied scripts or remote commands can affect runtime systems (spawn objects, change camera, run AI agents).
Practical integration points for developers:
- To expose new KĀDI commands, add ScriptMethod entries in GameScriptInterface::InitializeMethodRegistry and implement the corresponding Execute* handlers.
- To respond to broker-driven hot-reload/file events, hook FileWatcher events to submit a GenericCommand that will schedule a script reload through the JS worker (via JSGameLogicJob synchronization).
- Keep blocking or long-running operations out of main thread; route remote broker requests through the generic command queues and handle them on the worker thread or an I/O thread.
Where to look in the codebase
Section titled “Where to look in the codebase”- Engine/game lifecycle & bindings:
- Code/Game/Framework/App.hpp
- Code/Game/Framework/GameScriptInterface.hpp
- JS worker orchestration:
- Code/Game/Framework/JSGameLogicJob.hpp
- Code/Game/Gameplay/Game.hpp
- JavaScript framework & systems:
- Run/Data/Scripts/main.js
- Run/Data/Scripts/JSEngine.js
- Run/Data/Scripts/JSGame.js
- Run/Data/Scripts/InputSystemCommon.js
- Build preferences and toggles:
- Code/Game/EngineBuildPreferences.hpp
If you plan to modify scripting or hot-reload behavior:
- Start in JSGameLogicJob to understand how V8 isolate lifetime and worker synchronization are implemented.
- Update Game and GameScriptInterface when adding new engine services you want script-accessible.
- Validate JS-side registration in JSEngine.js and ensure global APIs are created in App::SetupScriptingBindings.
This documentation focuses on architecture and interaction surfaces; consult the listed headers and Run/Data/Scripts files for implementation details and follow the provided code patterns when extending VM bindings or adding new script-exposed functionality.