File size: 4,048 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
#pragma once
#include <cstdint>
#include <vector>
#include <cassert>
#include <functional>

namespace unify {

/// Generational handle: 24-bit slot index + 8-bit generation, packed in 32 bits.
/// Handles are what cross subsystem and script boundaries; raw pointers never do.
/// A handle whose generation no longer matches its slot is stale and fails validation.
template <typename Tag>
struct Handle {
    uint32_t bits = 0;  // 0 == null (generation 0 is never issued)
    static constexpr uint32_t kIndexBits = 24;
    static constexpr uint32_t kIndexMask = (1u << kIndexBits) - 1;

    static Handle make(uint32_t index, uint8_t gen) { return Handle{(uint32_t(gen) << kIndexBits) | (index & kIndexMask)}; }
    uint32_t index() const { return bits & kIndexMask; }
    uint8_t generation() const { return uint8_t(bits >> kIndexBits); }
    bool is_null() const { return bits == 0; }
    explicit operator bool() const { return bits != 0; }
    bool operator==(const Handle& o) const { return bits == o.bits; }
    bool operator!=(const Handle& o) const { return bits != o.bits; }
    bool operator<(const Handle& o) const { return bits < o.bits; }
};

struct EntityTag {};
struct TextureTag {};
struct AudioTag {};
struct PhysicsBodyTag {};
struct BufferTag {};
struct KernelTag {};
struct AssetTag {};
struct ClipTag {};

using EntityHandle = Handle<EntityTag>;
using TextureHandle = Handle<TextureTag>;
using AudioHandle = Handle<AudioTag>;        // a playing voice
using PhysicsBodyHandle = Handle<PhysicsBodyTag>;
using BufferHandle = Handle<BufferTag>;
using KernelHandle = Handle<KernelTag>;
using AssetHandle = Handle<AssetTag>;

/// Issues and validates generational handles over a dense slot range with a free list.
/// Free order is LIFO and fully deterministic, so the same sequence of alloc/free calls
/// always yields the same handles (required for deterministic replay and save states).
template <typename H>
class HandlePool {
public:
    H allocate() {
        uint32_t index;
        if (!free_.empty()) {
            index = free_.back();
            free_.pop_back();
        } else {
            index = uint32_t(gens_.size());
            assert(index <= H::kIndexMask);
            gens_.push_back(1);
            live_.push_back(0);
        }
        live_[index] = 1;
        alive_++;
        return H::make(index, gens_[index]);
    }

    bool valid(H h) const {
        uint32_t i = h.index();
        return !h.is_null() && i < gens_.size() && live_[i] && gens_[i] == h.generation();
    }

    bool release(H h) {
        if (!valid(h)) return false;
        uint8_t& g = gens_[h.index()];
        g = uint8_t(g + 1);
        if (g == 0) g = 1;  // skip 0 so a recycled slot never re-issues null
        live_[h.index()] = 0;
        free_.push_back(h.index());
        alive_--;
        return true;
    }

    /// Handle currently occupying a slot (for iteration); null if the slot is free.
    H at(uint32_t index) const { return index < gens_.size() && live_[index] ? H::make(index, gens_[index]) : H{}; }

    uint32_t alive() const { return alive_; }
    uint32_t capacity() const { return uint32_t(gens_.size()); }
    void reserve(size_t n) { gens_.reserve(n); free_.reserve(n); live_.reserve(n); }

    /// Raw state for save/restore: generations, liveness and free-list order.
    struct State { std::vector<uint8_t> gens; std::vector<uint8_t> live; std::vector<uint32_t> free; uint32_t alive = 0; };
    State save() const { return {gens_, live_, free_, alive_}; }
    void load(const State& s) { gens_ = s.gens; live_ = s.live; free_ = s.free; alive_ = s.alive; }
    void clear() { gens_.clear(); live_.clear(); free_.clear(); alive_ = 0; }

private:
    std::vector<uint8_t> gens_;
    std::vector<uint8_t> live_;
    std::vector<uint32_t> free_;
    uint32_t alive_ = 0;
};

}  // namespace unify

namespace std {
template <typename T>
struct hash<unify::Handle<T>> {
    size_t operator()(const unify::Handle<T>& h) const noexcept { return std::hash<uint32_t>()(h.bits); }
};
}  // namespace std