Hey there guys…
I’m trying to create a checkpoint system, but as my game is a puzzle game, i would like the “state” of the puzzle to be saved when triggering the the checkpoint… and then be loaded if the character die…
so for example, if a rock falls under the character, i could redo that scene.
The typical way of solving this would be to create an interface, and give each actor the task to save and load themselves (without having to reload the scene).
But i was thinking of something like serializing a snapshot of every actor (with their corresponding names) and de-serialize them on checkpoint load (but that could be slow).
Awesome… i had checked your post before and was fairly convinced on going that way…
just have a question, are there no issues when loading the objects from memory with dangling pointers ?
i.e.
object A has B as reference
B reloads (old B dies, new B is born)
What happens to a->b? (A does not have the interface implemented, like a level blueprint)
Sorry for the delayed response, I was gone for the holidays.
Repairing pointer references was the hardest part to figure out. If not for pointers, serializing data would be relatively easy. Here’s roughly the problem and the technique I came up with…
The problem is that any time you create a new object in memory, the objects address in memory will be different through every play session. If you play your game twice and instantiate memory for the same object, there is no guarantee that it will be the same memory address. For the sake of principle, we can treat the memory address of an object as a random number which changes per instance. Therefore, we don’t want to store the random number because its meaningless. So, let’s say you have object A and object B, which looks like this in memory:
Play Session 1:
0x004234 Object A
0x00FFAB Object B
Play Session 2:
0xDEADBEEF Object A
0x00112233 Object B
If you have a->b in session #1, and you try to serialize the memory address of object b (0x00FFAB), and then you create play session #2 and create two instances of Object A and Object B, then there is a 0% chance that Object B in session #2 will have the exact same memory address it had in Session #1. In other words: &ObjectB (session 1) != &ObjectB (session 2).
So, the technique I use is to create an indexed list of all objects to be serialized (put them into an array, indexed by unique ID). After every object is in an array, it has an array index which can be treated exactly like a memory address. If Object A is at index #0 and Object B is at index #1, then to preserve the pointer reference, we convert the memory address into an array index value. So, a->b gets serialized such that b == 1. When I go to load your objects back into memory from disk, I do a two pass deserialization sequence. The first pass creates every object in memory from the serialized data, but every pointer reference is initially NULL. The first pass also recreates the array of objects, so that we have an indexed list of every object which was instanced. We know that the array index of the object corresponds to the array sequence we used when serializing, so our loaded object array exactly matches what we had during serialization. The second pass now just has to go through every object and relink pointers. So, since ObjectA contains a reference to ObjectB and its encoded as index #1, we just go into object A, find the pointer we want to repair (a->b), lookup the object in index #1, and set (a->b = yourArray[1]). At this point, you don’t know or care about what the memory address of object B was, you just care about repairing the pointer reference and recreating the correct game state.
The downside to this technique is that I don’t know what pointers or data structures an object contains or needs to preserve, so its necessary for me to force an interface implementation and let every object decide for itself how to serialize / deserialize itself. This creates a bit of overhead work for projects which have a lot of instanced objects to serialize, but it’s also pretty powerful. If you had a thousand pointers, or a linked list of pointers, tree, or a graph of pointers, I totally support the serialization of it. You can also have pointers which go in a loop, such that:
a->b
b->a
and serializing this relationship is not a problem. If someone uses a recursive technique, this would cause an infinite loop if they don’t implement a visitor design pattern of some sort.
Now…
I know that the UE4 engineers have come up with a much more elegant and transparent technique for serializing/deserializing pointer references. Whatever they’re doing, it’s currently black magic to me. But, consider that the engine supports multiplayer. This means that the game state has to be replicated on all connected clients and every connected client needs to have the same data models. So, if the server has a->b, client #1 must also have a->b, and the memory addresses of “a” and “b” on the client and server will be different – so there must be a way in which the server and client reference the same objects without using memory addresses. I haven’t played with networking enough yet to dig into how exactly this works, but I think this proves with certainty that there’s a way to store object references without requiring an interface. I just don’t know how they do it. I need to do more research & development in this area, but what I have now works relatively elegantly.
Things referenced in multiplayer use the actor name (like “Actor_473”) rather than the physical memory pointer. This means that all references that are replicated must be references to something derived from UObject (like AActor).