Core¶
The core layer owns the application lifecycle and global services. It is centered on snowpulse::App and snowpulse::AppContext.
App¶
App sets up the engine services, manages the main update/render loop, and owns the active Scene.
Key responsibilities:
- Creates and initializes core systems (input, audio, assets, renderer, analytics, event bus, and profile-enabled HTTP, MIDI, saves, video, ImGui, and Effekseer).
- Runs the update loop and renders each frame.
- Stores a
bundleIdand apersistentDataPathfor saves. - Boots
SaveSystemreadiness duringApp::init(). - Selects an
IWebPortalAPIadapter and keeps its audio/pause state synchronized with the engine. - Lets Android applications consume system Back by overriding
onBackRequested(); returningfalseperforms the platform default exit.
Frame Update Order¶
Per frame, App executes scene work in this order:
- Deferred asset-pack callbacks and save-system work
- HTTP, MIDI, and video servicing
scene->update(dt)(scene-level logic)scene->updateComponents(dt)(Updatablepass + physics step)scene->lateUpdateComponents(dt)(LateUpdatablepass)scene->updateUI(dt)- queued event dispatch
- pending node and UI destruction
- Effekseer and audio servicing
Portal pause can suppress scene work and rendering. Playgama continues save and audio servicing while paused; YouTube freezes the complete engine frame until the portal resumes it.
AppConfig¶
AppConfig provides:
- bundleId (feeds into save storage and the persistent data path)
- windowTitle
- consumeMouseWheel (default true, controls whether the web canvas captures mouse wheel events)
- saveSyncPolicy (Local by default)
- saveInitializeTimeoutSeconds (remote init timeout used by SaveSystem)
- saveRemoteLoadTimeoutSeconds (timeout for one remote read attempt before retry)
- webPortal (Default by default; CMake target presets populate it on web)
snowpulse_add_game() generates <snowpulse/target_config.h> with the target's
bundle ID, display name, portal, and resolved save policy. Construct the app
from snowpulse::target_config::makeAppConfig() so these values are applied
before services initialize.
Save Startup Readiness¶
When saveSyncPolicy is Remote, scene activation is deferred until save
bootstrap is ready (or timeout). This keeps application startup aligned with
provider availability without adding platform-specific scene branching.
If remote init fails or times out, startup still continues while initialization retries. Remote operations remain queued during transient unavailability and complete only after recovery or a terminal failure.
AppContext¶
AppContext is a shared container for engine systems. It is accessed by components and scenes through helper accessors.
Notable systems:
- Input
- AssetManager
- AudioManager
- RenderQueue and IRenderer
- Scene
- UIElementRegistry
- EventBus
- SaveSystem
- IHttpClient
- IMIDIManager
- IFileDialogService (fileDialogs(), with native desktop backends)
- IWebPortalAPI
- VideoPlayer
- Analytics (always present; selected through AppConfig::analytics)
- Time
- ImGuiLayer (optional debug UI)
It also stores:
targetResolution, livelogicalResolution, and desktopdesktopWindowConfig/compatibilitydesktopWindowSizedesktopRecorderConfig(disabled normally; opt-in for macOS marketing builds)physicsInterpolationandphysicsScaleFactor- live screen orientation via
screenOrientation(),isPortrait(), andisLandscape() assetRoot- the active
assetSource()and logicalsafeAreaInsets() bundleIdpersistentDataPathconsumeMouseWheel
AppContext::quit() requests the app to shut down (useful for menus or scripts that need to exit).
desktopWindowSize(width, height) sets the initial desktop window size without
changing the target resolution. Passing a non-positive dimension clears
the override. On web, iOS, and Android, platform code determines the presentation size.
Use desktopWindowConfig() for runtime windowed, borderless, and exclusive
fullscreen transitions; see Desktop Production.
physicsInterpolation(false) disables interpolation between fixed Box2D steps.
See Physics for the visual and synchronization implications.
Several services are compile-time gated by the selected web profile. Guard direct references with the matching definitions:
The current gates are SNOWPULSE_HAS_HTTP, SNOWPULSE_HAS_MIDI,
SNOWPULSE_HAS_VIDEO, SNOWPULSE_HAS_SAVES, SNOWPULSE_HAS_IMGUI, and
SNOWPULSE_HAS_EFFEKSEER. Analytics is not API-gated: playable builds set
SNOWPULSE_HAS_ANALYTICS_SDKS=0, but analytics() remains available through
its null backend. See Analytics.
screenOrientation() returns ScreenOrientation::Portrait or
ScreenOrientation::Landscape. On mobile web it follows the device/browser
screen orientation. On native desktop, iOS, Android, and desktop web it follows the
logical viewport aspect ratio; square viewports are treated as portrait.
Android renders edge-to-edge. safeAreaInsets() returns left/top/right/bottom
in logical coordinates so an application or UIRoot can opt into inset padding.
See Android.
Time¶
Time measures delta time (dt) and total time since start, both in seconds.
The platform runner calls init() and tick(); application code should read
dt() and total() rather than advancing the clock itself.
Runtime Display Settings¶
vsync(bool)stores the setting and immediately forwards it to an existing platform window. Desktop and Android apply the toggle; browser scheduling and iOSCADisplayLinkcurrently keep their host-controlled cadence.debugMode(bool)is a shared application/debug-tooling flag. It does not create a debug overlay by itself.textureFiltering(TextureFiltering)selects the render queue's global texture-filtering default. Renderers and UI elements set toTextureFiltering::Inherituse it; components may override it individually.windowTitle(string)stores the host title. PreferAppConfig::windowTitlebecause desktop and web hosts read it beforeApp::init(); changing the context value later does not rename an already-created native window.
Sample Usage¶
#include <snowpulse/target_config.h>
namespace {
snowpulse::AppConfig createAppConfig() {
auto config = snowpulse::target_config::makeAppConfig();
config.consumeMouseWheel = true;
return config;
}
}
class SampleApp final : public snowpulse::App {
public:
SampleApp() : snowpulse::App(createAppConfig()) {}
bool init() override {
if (!snowpulse::App::init()) {
return false;
}
auto* appContext = context();
appContext->assetRoot("assets");
appContext->vsync(true);
appContext->debugMode(true);
appContext->textureFiltering(snowpulse::TextureFiltering::Nearest);
setScene(std::make_unique<StartupScene>());
return true;
}
void update(const float& dt) override {
snowpulse::App::update(dt);
const auto* clock = context()->time();
_frameSeconds = clock ? clock->dt() : static_cast<double>(dt);
_elapsedSeconds = clock ? clock->total() : _elapsedSeconds + dt;
}
private:
double _frameSeconds = 0.0;
double _elapsedSeconds = 0.0;
};