File size: 4,593 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
#pragma once
#include <cstdint>
#include <cstdio>
#include <cstdarg>
#include <string>

namespace unify {

// ---------------------------------------------------------------------------------------
// Deterministic RNG. The ONLY random source simulation code may use (engine, physics, Lua).
// xoshiro128** — 128-bit state, fully serializable, identical output on every platform.
// ---------------------------------------------------------------------------------------
class Rng {
public:
    explicit Rng(uint64_t seed = 0x5eed) { reseed(seed); }
    void reseed(uint64_t seed) {
        // SplitMix64 expands the seed so nearby seeds give unrelated streams.
        for (int i = 0; i < 4; i += 2) {
            seed += 0x9E3779B97F4A7C15ull;
            uint64_t z = seed;
            z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ull;
            z = (z ^ (z >> 27)) * 0x94D049BB133111EBull;
            z ^= z >> 31;
            s_[i] = uint32_t(z);
            s_[i + 1] = uint32_t(z >> 32);
        }
        if ((s_[0] | s_[1] | s_[2] | s_[3]) == 0) s_[0] = 1;
    }
    uint32_t next_u32() {
        uint32_t result = rotl(s_[1] * 5, 7) * 9;
        uint32_t t = s_[1] << 9;
        s_[2] ^= s_[0]; s_[3] ^= s_[1]; s_[1] ^= s_[2]; s_[0] ^= s_[3];
        s_[2] ^= t; s_[3] = rotl(s_[3], 11);
        return result;
    }
    /// [0, 1) with 24 bits of precision: exactly representable in float, no rounding drift.
    float next_float() { return float(next_u32() >> 8) * (1.0f / 16777216.0f); }
    float range(float lo, float hi) { return lo + (hi - lo) * next_float(); }
    int32_t range_int(int32_t lo, int32_t hi_inclusive) {
        uint32_t span = uint32_t(hi_inclusive - lo) + 1;
        return span == 0 ? int32_t(next_u32()) : lo + int32_t(next_u32() % span);
    }
    const uint32_t* state() const { return s_; }
    void set_state(const uint32_t st[4]) { for (int i = 0; i < 4; i++) s_[i] = st[i]; }

private:
    static uint32_t rotl(uint32_t x, int k) { return (x << k) | (x >> (32 - k)); }
    uint32_t s_[4];
};

// ---------------------------------------------------------------------------------------
// Clocks. Five independent notions of time; only SimClock is visible to gameplay.
// ---------------------------------------------------------------------------------------

/// Wall clock: platform monotonic time in nanoseconds. Nondeterministic; never read by simulation.
uint64_t wall_clock_ns();

/// Simulation time: an integer tick counter. Seconds are derived, never accumulated,
/// so there is no floating-point drift across long sessions or save/restore.
struct SimClock {
    uint64_t tick = 0;
    uint32_t ticks_per_second = 60;
    double dt() const { return 1.0 / ticks_per_second; }
    float dtf() const { return 1.0f / float(ticks_per_second); }
    double seconds() const { return double(tick) / ticks_per_second; }
    /// Input timestamps are nanoseconds since engine start on the input clock; tick N covers
    /// the half-open interval [N*dt, (N+1)*dt).
    uint64_t tick_end_ns(uint64_t t) const { return (t + 1) * 1000000000ull / ticks_per_second; }
};

/// Render time: wall time of the current frame plus the interpolation factor between the
/// previous and current simulation states.
struct RenderClock {
    uint64_t frame = 0;
    double seconds = 0;
    float alpha = 0;       // 0..1 blend between previous and current sim state
    double frame_ms = 0;
};

// ---------------------------------------------------------------------------------------
// Logging
// ---------------------------------------------------------------------------------------
enum class LogLevel { Debug, Info, Warn, Error };
void log_message(LogLevel level, const char* category, const char* fmt, ...)
#if defined(__GNUC__)
    __attribute__((format(printf, 3, 4)))
#endif
    ;
void set_log_level(LogLevel min_level);

#define UNIFY_LOG_INFO(cat, ...) ::unify::log_message(::unify::LogLevel::Info, cat, __VA_ARGS__)
#define UNIFY_LOG_WARN(cat, ...) ::unify::log_message(::unify::LogLevel::Warn, cat, __VA_ARGS__)
#define UNIFY_LOG_ERROR(cat, ...) ::unify::log_message(::unify::LogLevel::Error, cat, __VA_ARGS__)
#define UNIFY_LOG_DEBUG(cat, ...) ::unify::log_message(::unify::LogLevel::Debug, cat, __VA_ARGS__)

/// FNV-1a 64 over bytes; used for state hashes (determinism checks, rollback verification).
inline uint64_t fnv1a(const void* data, size_t n, uint64_t h = 1469598103934665603ull) {
    auto p = static_cast<const uint8_t*>(data);
    for (size_t i = 0; i < n; i++) { h ^= p[i]; h *= 1099511628211ull; }
    return h;
}

}  // namespace unify