Skip to content

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 bundleId and a persistentDataPath for saves.
  • Boots SaveSystem readiness during App::init().
  • Selects an IWebPortalAPI adapter and keeps its audio/pause state synchronized with the engine.
  • Lets Android applications consume system Back by overriding onBackRequested(); returning false performs the platform default exit.

Frame Update Order

Per frame, App executes scene work in this order:

  1. Deferred asset-pack callbacks and save-system work
  2. HTTP, MIDI, and video servicing
  3. scene->update(dt) (scene-level logic)
  4. scene->updateComponents(dt) (Updatable pass + physics step)
  5. scene->lateUpdateComponents(dt) (LateUpdatable pass)
  6. scene->updateUI(dt)
  7. queued event dispatch
  8. pending node and UI destruction
  9. 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, live logicalResolution, and desktop desktopWindowConfig/compatibility desktopWindowSize
  • desktopRecorderConfig (disabled normally; opt-in for macOS marketing builds)
  • physicsInterpolation and physicsScaleFactor
  • live screen orientation via screenOrientation(), isPortrait(), and isLandscape()
  • assetRoot
  • the active assetSource() and logical safeAreaInsets()
  • bundleId
  • persistentDataPath
  • consumeMouseWheel

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:

#if SNOWPULSE_HAS_VIDEO
auto* video = context()->videoPlayer();
#endif

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 iOS CADisplayLink currently 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 to TextureFiltering::Inherit use it; components may override it individually.
  • windowTitle(string) stores the host title. Prefer AppConfig::windowTitle because desktop and web hosts read it before App::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;
};