// core.cm - Minimal entry point for prosperon rendering // // Usage: // var core = use('prosperon/core') // core.start({ // width: 1280, // height: 720, // title: "My Game", // update: function(dt) { ... }, // render: function() { return graph } // }) // // Headless mode (no window, no input, no render, no audio): // core.start({ headless: true, update: function(dt) { ... } }) var io = use('cellfs') io.mount('/Users/john/work/prosperon') var time_mod = use('time') var core = {} // Private state var _running = false var _config = null var _backend = null var _window = null var _last_time = 0 var _framerate = 60 // Lazy-loaded modules (only in non-headless mode) var video = null var events = null var imgui = null var debug_imgui = null // Start the application core.start = function(config) { _config = config _framerate = config.framerate || 60 _running = true _last_time = time_mod.number() if (config.probe) _register_probes() if (config.headless) { _headless_loop() return true } // Load SDL modules video = use('sdl3/video') events = use('sdl3/input') imgui = use('imgui') debug_imgui = use('debug_imgui') // Initialize SDL GPU backend var sdl_gpu = use('sdl_gpu') _backend = sdl_gpu var init_result = _backend.init({ width: config.width || 1280, height: config.height || 720, title: config.title || "Prosperon" }) if (!init_result) { log.console("core: Failed to initialize backend") return false } _window = _backend.get_window() if ((config.imgui || config.editor) && imgui.init) { imgui.init(_window, _backend.get_device()) } // Start main loop _main_loop() // Call start callback after the first frame and main loop are established if (config.start) config.start() return true } // Stop the application core.stop = function() { _running = false } // Get window size core.window_size = function() { return _backend.get_window_size() } // Get backend for direct access core.backend = function() { return _backend } // FPS tracking var _fps_samples = [] var _fps_sample_count = 120 var _fps_sample_sum = 0 var _fps_sample_pos = 0 function fps_add_sample(sample) { var n = length(_fps_samples) var old = null if (n < _fps_sample_count) { _fps_samples[] = sample _fps_sample_sum += sample } else { old = _fps_samples[_fps_sample_pos] _fps_samples[_fps_sample_pos] = sample _fps_sample_sum += sample - old _fps_sample_pos++ if (_fps_sample_pos >= _fps_sample_count) _fps_sample_pos = 0 } } function fps_get_avg() { var n = length(_fps_samples) return n ? _fps_sample_sum / n : 0 } var _current_fps = 0 var _frame_time_ms = 0 // Headless loop — update only, no window/input/render/audio function _headless_loop() { if (!_running) return var now = time_mod.number() var dt = now - _last_time _last_time = now if (_config.update) _config.update(dt) var frame_time = 1 / _framerate var elapsed = time_mod.number() - now var delay = frame_time - elapsed if (delay < 0) delay = 0 $delay(_headless_loop, delay) } // Main loop function _main_loop() { var frame_start = time_mod.number() if (!_running) return var now = frame_start var dt = now - _last_time _last_time = now // Process events var evts = events.get_events() var win_size = _backend.get_window_size() // Get input module for auto-ingestion var input_mod = use('input') arrfor(evts, function(ev) { if (_config.imgui || _config.editor) { imgui.process_event(ev) } var ev_obj = events.objectify(ev) if (ev_obj.type == 'quit') { _running = false $stop() return } if (ev_obj.type == 'window_pixel_size_changed') { win_size.width = ev_obj.width win_size.height = ev_obj.height if (_backend.set_window_size) { _backend.set_window_size(ev_obj.width, ev_obj.height) } } // Auto-ingest through input system input_mod.ingest(ev_obj) // Optional raw hook for debugging if (_config.input) { _config.input(ev_obj) } }) // Update if (_config.update) { _config.update(dt) } var imgui_mod = use('imgui') var debug_imgui = use('debug_imgui') // ImGui Frame if (_config.imgui || _config.editor) { imgui_mod.newframe() } // Render var render_result = null var dbg = false var stats = null if (_config.render) { render_result = _config.render() if (render_result) { if (_config.debug == 'graph') { log.console(render_result) $stop() return } dbg = _config.debug == 'cmd' // Build stats for debug_imgui stats = { fps: _current_fps, frame_time_ms: _frame_time_ms } // Handle both compositor result ({commands: [...]}) and fx_graph (graph object) if (render_result.commands) { if (_config.imgui || _config.editor) { render_result.commands[] = { cmd: 'imgui', draw: function(ui) { if (_config.imgui) _config.imgui(ui) if (is_function(_config.editor)) { debug_imgui.render(ui, null, render_result.plan, stats) _config.editor(ui) } }, target: 'screen' } } // Compositor result - execute commands directly _backend.execute_commands(render_result.commands, win_size, dbg) } else { // fx_graph result - execute graph _backend.execute_graph(render_result, win_size, dbg) } if (dbg) { $stop() return } } } var frame_end = time_mod.number() var actual_frame_time = frame_end - frame_start _frame_time_ms = actual_frame_time * 1000 fps_add_sample(actual_frame_time) var avg_frame_time = fps_get_avg() _current_fps = avg_frame_time > 0 ? 1 / avg_frame_time : 0 // Schedule next frame var frame_time = 1 / _framerate var elapsed = frame_end - frame_start var delay = frame_time - elapsed if (delay < 0) delay = 0 $delay(_main_loop, delay) } function _register_probes() { var probe = use('probe') var film2d = use('film2d') var world = use('world') var comp = use('compositor') var input_mod = use('input') var tween_mod = use('tween') var graphics_mod = use('graphics') probe.register("drawables", { all: function(args) { return film2d.snapshot() } }) probe.register("world", { all: function(args) { return world.snapshot() }, count: function(args) { return world.count() } }) probe.register("compositor", { all: function(args) { return comp.snapshot() } }) probe.register("input", { all: function(args) { return input_mod.snapshot() } }) probe.register("tweens", { all: function(args) { return tween_mod.snapshot() } }) probe.register("assets", { all: function(args) { return graphics_mod.snapshot() } }) probe.register("core", { fps: function(args) { return {fps: _current_fps, frame_time_ms: _frame_time_ms} } }) } return core