File size: 8,493 Bytes
3ce1a40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
#pragma once
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "backend.h"
#include "../core/core.h"
#include "../ecs/ecs.h"
#include "../physics/physics.h"
#include "../animation/animation.h"
#include "../assets/assets.h"
#include "../script/lua_vm.h"
#include "../wasm/compute.h"
#include "../memory/memory.h"

namespace unify {

/// Scheduler stages, in execution order. Recorded per tick/frame for tracing and tests.
enum class Stage : uint8_t { Input, InputBuffer, FixedSimulation, Physics, Gameplay, Animation, Audio, RenderPrep, Render, Present, Count };
const char* stage_name(Stage s);

struct PrefabDef {
    std::string name;
    bool has_sprite = false;
    std::string texture, region;
    Vec2 sprite_size{1, 1};
    int16_t layer = 0;
    float depth = 0;
    Color tint;
    bool has_body = false;
    BodyDef body;
    std::string animation;   // initial clip
    uint32_t tags = 0;
};

struct Timer { uint64_t fire_tick; uint64_t interval_ticks; std::string name; uint16_t scene; };

/// A loaded scene: its script table and every resource it owns. Unloading a scene frees
/// all of it, which is what makes transitions leak-free.
struct Scene {
    uint16_t id = 0;
    std::string name;
    int lua_ref = -1;                 // registry ref to the table returned by the scene script
    AssetHandle script;
    std::vector<AssetHandle> assets;
    std::vector<AudioHandle> voices;
    std::vector<BufferHandle> buffers;
    std::vector<KernelHandle> kernels;
};

struct ResourceCounts {
    uint32_t entities, bodies, anim_players, voices, buffers, kernels, assets, textures, lua_refs;
    bool operator==(const ResourceCounts& o) const {
        return entities == o.entities && bodies == o.bodies && anim_players == o.anim_players && voices == o.voices && buffers == o.buffers &&
               kernels == o.kernels && assets == o.assets && textures == o.textures && lua_refs == o.lua_refs;
    }
};

struct FrameStats {
    uint32_t ticks_this_frame = 0;
    double stage_ms[size_t(Stage::Count)] = {};
    uint64_t heap_allocs_last_tick = 0;
    size_t lua_bytes = 0;
};

class Engine {
public:
    static constexpr uint32_t kSaveVersion = 1;
    static constexpr const char* kEngineVersion = "UNIFY 1.0";

    Engine(Backend& backend, EngineConfig config);
    ~Engine();

    /// BOOT β†’ LOAD ASSETS β†’ CREATE WORLD: starts devices, Lua, and the boot scene.
    bool boot(std::string& error);
    /// One real-time frame: poll input, run due fixed ticks, render, present.
    void frame();
    /// One fixed simulation tick (INPUT BUFFER β†’ FIXED SIM β†’ PHYSICS β†’ GAMEPLAY β†’ ANIMATION β†’ AUDIO).
    void tick() { tick_impl(nullptr); }
    /// Runs one tick using a recorded/networked InputFrame instead of live input. This is
    /// the replay / rollback path: same state + same frames = same result.
    void replay_tick(const InputFrame& frame) { tick_impl(&frame); }
    /// RENDER PREP β†’ RENDER β†’ PRESENT with interpolation factor alpha.
    void render(float alpha);
    /// Runs until the backend or gameplay requests quit.
    int run();
    bool quit_requested() const { return quit_; }
    void request_quit() { quit_ = true; }

    // ---- save states --------------------------------------------------------------------
    std::vector<uint8_t> save_state(bool include_audio = true);
    bool load_state(const std::vector<uint8_t>& bytes, std::string& error);
    std::vector<uint8_t> snapshot() { return save_state(); }
    bool restore(const std::vector<uint8_t>& s, std::string& e) { return load_state(s, e); }
    bool save_to_file(const std::string& slot, std::string& error);
    bool load_from_file(const std::string& slot, std::string& error);
    /// Hash of all deterministic simulation state (excludes audio playback, which runs on
    /// its own clock). Equal hashes = equal simulations: the lockstep/rollback check.
    uint64_t state_hash();

    // ---- scenes / entities ----------------------------------------------------------------
    bool transition(const std::string& scene, std::string& error);
    bool push_scene(const std::string& scene, std::string& error);
    bool pop_scene(std::string& error);
    const Scene* active_scene() const { return scenes_.empty() ? nullptr : &scenes_.back(); }
    EntityHandle instantiate(const std::string& prefab, Vec2 position, bool persistent, std::string& error);
    bool destroy_entity(EntityHandle e);
    ResourceCounts resources() const;

    // ---- input injection (headless / replay) ----------------------------------------------
    /// Queues a button event that lands inside the next simulation tick's input window.
    void inject(uint16_t code, bool down);
    uint64_t next_tick_input_ns() const;

    // ---- subsystems -------------------------------------------------------------------------
    World& world() { return world_; }
    PhysicsWorld& physics() { return physics_; }
    AnimationSystem& animation() { return anim_; }
    InputSystem& input() { return input_; }
    Mixer& mixer() { return mixer_; }
    AssetManager& assets() { return *assets_; }
    ComputeRuntime& compute() { return compute_; }
    LuaVM& lua() { return *lua_; }
    Renderer2D& renderer() { return *renderer_; }
    Camera& camera() { return camera_; }
    Rng& rng() { return rng_; }
    const SimClock& sim_clock() const { return sim_; }
    const RenderClock& render_clock() const { return render_clock_; }
    RenderTarget& target() { return target_; }
    Backend& backend() { return backend_; }
    const EngineConfig& config() const { return config_; }
    const FrameStats& stats() const { return stats_; }
    const std::vector<Stage>& trace() const { return trace_; }
    const std::vector<std::string>& log_lines() const { return script_log_; }
    const PrefabDef* prefab(const std::string& name, std::string& error);

    // Used by bindings.
    bool in_render_phase() const { return rendering_; }
    void add_timer(double seconds, bool repeat, const std::string& name);
    void request_transition(const std::string& scene) { pending_transition_ = scene; }
    void request_save(const std::string& slot) { pending_ops_.push_back({true, slot}); }
    void request_load(const std::string& slot) { pending_ops_.push_back({false, slot}); }
    std::vector<uint8_t>& quick_slot() { return quick_slot_; }
    void script_log(const std::string& s);
    Scene* current_scene() { return scenes_.empty() ? nullptr : &scenes_.back(); }
    const BitmapFont& font() const { return font_; }
    const TextureAsset* texture(const std::string& name);
    void report_script_error(const std::string& where);

private:
    void tick_impl(const InputFrame* replay);
    bool load_scene_script(Scene& s, std::string& error);
    void unload_scene(Scene& s);
    bool call_scene(const char* fn, int nargs = 0);
    void sync_transforms_from_physics();
    void dispatch_physics_events();
    void fire_timers();
    void process_pending_state_ops();
    void mark(Stage s) { if (trace_.size() >= 64) trace_.clear(); trace_.push_back(s); }

    Backend& backend_;
    EngineConfig config_;
    World world_;
    PhysicsWorld physics_;
    AnimationSystem anim_;
    InputSystem input_;
    Mixer mixer_;
    AudioStreamer streamer_;
    std::unique_ptr<AssetManager> assets_;
    ComputeRuntime compute_;
    std::unique_ptr<LuaVM> lua_;
    std::unique_ptr<Renderer2D> renderer_;
    RenderTarget target_;
    Camera camera_;
    BitmapFont font_;
    Rng rng_;
    SimClock sim_;
    RenderClock render_clock_;
    FrameAllocator frame_alloc_{1u << 20};
    uint64_t sim_origin_ns_ = 0;     // input-clock time at which tick 0 began
    std::vector<Scene> scenes_;
    uint16_t next_scene_id_ = 1;
    std::vector<Timer> timers_;
    std::map<std::string, PrefabDef> prefabs_;
    std::map<std::string, AssetHandle> texture_cache_;
    std::vector<ContactEvent> events_scratch_;
    std::vector<Stage> trace_;
    FrameStats stats_;
    std::vector<std::string> script_log_;
    std::vector<uint8_t> quick_slot_;
    std::vector<std::pair<bool, std::string>> pending_ops_;  // (is_save, slot); "" = quick slot
    bool quit_ = false, booted_ = false, rendering_ = false;
    std::string pending_transition_;
};

/// Registers the UNIFY Lua API (entity, world, physics, audio, input, camera, ui, timer,
/// scene, buffer, kernel, gc, save_state/load_state) into the engine's Lua state.
void register_bindings(Engine& engine);

}  // namespace unify