-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathreferences.cpp
More file actions
171 lines (145 loc) · 5.13 KB
/
Copy pathreferences.cpp
File metadata and controls
171 lines (145 loc) · 5.13 KB
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
// [references]
// Supporting references in Savepoint comes with some issues.
//
// 1. I need to be able to load anything on demand. If Savepoint loads an entity
// with a reference, Savepoint needs to load the entity to ensure it exists in a
// shared_ptr. That means that you can only reference SAVEPOINT_POLY entities
// (since they're the only entities that provide Savepoint with a factory).
// To do that, I'd need another class that inherits both SavepointEntity and SavepointPoly.
//
// 2. The roles get a bit messy as well. When visiting a reference, the visitor would
// have to query the Savepoint to load the entity on demand. Currently, the visitor
// doesn't have to care about the Savepoint.
//
// 3. To avoid loading the same entity multiple times, Savepoint needs to keep track
// of all loaded entities AND avoid loading them again when requested to load all
// entities.
//
// 4. Since there's currently a single visitor per Savepoint, I'd have to recursively
// create visitors when entities are loaded on demand. Otherwise you'll clobber the
// current visitor.
//
// 5. Loaded entities have to track what level they were loaded from. Typically the user
// is expected to assign the entity to a level. Since the user didn't ask for that entity
// explicitely, we don't know what level it belongs to (without asking the driver).
//
// 6. Look how much simpler the following code is.
#include <savepoint/savepoint.hpp>
#include <cassert>
#include <filesystem>
#include <map>
#include <memory>
#include <memory>
#include <unordered_map>
#include <vector>
static constexpr SavepointVersion kVersion{0, 0, 0};
struct PolyEntity : SavepointEntity, SavepointPoly
{
virtual void OnDeserialized() {}
virtual bool IsValid() const { return true; }
};
// IDs are guaranteed to be unique. Since they can be serialized, you can safely map them to entities
static std::unordered_map<SavepointID, std::weak_ptr<PolyEntity>> References;
// You'll probably want a list of strong references for your entities
static std::vector<std::shared_ptr<PolyEntity>> Entities;
struct EntityReference
{
// The concrete ID to the entity
SavepointID ID;
// A weak reference to the entity
std::weak_ptr<PolyEntity> Entity;
void Visit(SavepointVisitor& visitor)
{
// Don't serialize Entity, only the ID
visitor(ID);
}
void SetEntity(const std::shared_ptr<PolyEntity>& entity)
{
ID = entity->GetID();
assert(ID.IsValid());
Entity = entity;
}
void OnDeserialized()
{
// Search for the reference using the serialized ID
assert(ID.IsValid());
auto it = References.find(ID);
if (it != References.end())
{
Entity = it->second;
}
else
{
ID = SavepointID{};
}
}
bool IsValid() const
{
return ID.IsValid() && !Entity.expired();
}
};
struct PlayerEntity : PolyEntity
{
SAVEPOINT_POLY(PlayerEntity)
};
struct ZombieEntity : PolyEntity
{
SAVEPOINT_POLY(ZombieEntity)
EntityReference Player;
void Visit(SavepointVisitor& visitor)
{
PolyEntity::Visit(visitor);
visitor(Player);
}
void OnDeserialized() override
{
Player.OnDeserialized();
}
bool IsValid() const override
{
return Player.IsValid() && Player.Entity.lock()->GetClassName() == "PlayerEntity";
}
};
int main()
{
std::filesystem::remove("savepoint.sqlite3");
Savepoint savepoint;
savepoint.Open(SavepointDriver::SQLite3, "savepoint.sqlite3", kVersion);
std::shared_ptr<PlayerEntity> inPlayer1 = std::make_shared<PlayerEntity>();
std::shared_ptr<PlayerEntity> inPlayer2 = std::make_shared<PlayerEntity>();
std::shared_ptr<ZombieEntity> inZombie1 = std::make_shared<ZombieEntity>();
std::shared_ptr<ZombieEntity> inZombie2 = std::make_shared<ZombieEntity>();
std::shared_ptr<ZombieEntity> inZombie3 = std::make_shared<ZombieEntity>();
std::shared_ptr<ZombieEntity> inZombie4 = std::make_shared<ZombieEntity>();
// Sadly, a limitation is that you need a valid ID, which means you need to have already be written
savepoint.Write(inPlayer1, 0);
savepoint.Write(inPlayer2, 0);
savepoint.Write(inZombie1, 0);
savepoint.Write(inZombie2, 0);
savepoint.Write(inZombie3, 0);
savepoint.Write(inZombie4, 0);
// Now that everything has a valid ID, we can establish references
inZombie1->Player.SetEntity(inPlayer1);
inZombie2->Player.SetEntity(inPlayer1);
inZombie3->Player.SetEntity(inPlayer2);
inZombie4->Player.SetEntity(inPlayer2);
savepoint.Write(inZombie1, 0);
savepoint.Write(inZombie2, 0);
savepoint.Write(inZombie3, 0);
savepoint.Write(inZombie4, 0);
savepoint.Read<std::shared_ptr<PolyEntity>>([&](std::shared_ptr<PolyEntity>& entity)
{
References[entity->GetID()] = entity;
Entities.push_back(entity);
}, 0);
assert(References.size() == 6);
assert(Entities.size() == 6);
for (std::shared_ptr<PolyEntity>& entity : Entities)
{
entity->OnDeserialized();
assert(entity->IsValid());
}
savepoint.Close();
return 0;
}
// [references]