Skip to content

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):

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.

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):

  1. App::RunFrame / RunMainLoop triggers BeginFrame and signals worker to run JS for the upcoming frame.
  2. JSGameLogicJob wakes, acquires v8::Locker, executes registered JS systems (via JSEngine/JSGame), writes to EntityStateBuffer and/or submits render commands.
  3. Worker signals completion (condition_variable). Main thread swaps entity buffers, consumes render commands, and renders stable frame.
  4. 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

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):
JSGameLogicJob.hpp
// 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 Declarations
class IJSGameLogicContext; // Abstract interface for JavaScript execution context
class 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);
};

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 Point
import './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 Framework
export 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.
  • 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.
  • 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.