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
|