6
\$\begingroup\$

There is a theoretical mismatch of the entity system and the savegame system, which is think is common, but I haven't found resources about that. Therefore I will explain both systems first, as short as possible. I would really appreciate any thoughts and solutions for this conceptual problem.

Entity system

I have an entity system for my game. That means there is a function to create a new entity that simply returns a unique integer id. And there are functions to add, get and remove properties, thus any C++ objects, to a given id.

An entity id is just a contiguous integer, which is stored as a member. When a new entity is created, this number is incremented by one and returned as the new id. When entities get deleted, only all of their properties are removed and freed but their id actually remains and is not given to a new entity again.

Now you know the basics of my entity system. As an example, an entity might have properties for physics simulation, character animation, health and skill points, camera to render from its view, and so on.

(Originally, I described my entity system in more detail. But it wasn't directly related to this question and and wanted to shorten the question. It can still be viewed in the revision history.)

Savegame system

Previously, there was no savegame system and I created all entities as new ones on startup. But now I want to save the state when closing the application and load that on startup so that the player can continue where he left off.

I use SQLlite for storing, where each type of entity property gets its own table. I haven't decided yet if each row should store the entity id as primary key. Because this is part of my current problem.

Mismatch of both

Unfortunately, I cannot just store simply all of the entities to database, because some entities are only temporarily valid. For example the window property that holds a window handler from the operating system, which must be requested every startup. Or the meshes which aren't kept in memory, but just hold the VBO ids to the vertices on the GPU.

So I have to store some, but not all entities. I could store their entity id in the SQLite table and allow the entity system to add entities by a given new id. But I cannot guarantee that this id isn't already used by entities that where newly created at startup. Do you see the mismatch?

Can can I store the entity id to table, and still ensure that when being loaded, no other entity shares this id? Or how to restructure and rethink my entity system and savegame system to overcome this issue, in general?

\$\endgroup\$

3 Answers 3

4
\$\begingroup\$

A window handler should not be an entity property. As you said it will be retrieved every startup. So no need to store it as property. Store the handler in a subsystem responsible for dealing with windows.

Your meshes: the entity properties should contain everything to recreate the meshes, but not the meshes themselves. Read the necessary data from the properties to create the meshes, but hold the mesh ids in a render manager or something similiar.

Think about it that way: your entities and their properties represent state of the game world. They describe the state of the game world and your subsystems (rendering, physics, audio, ai etc.) read these properties and create any necessary internal objects (sprites, meshes, loading sounds etc.).

\$\endgroup\$
8
  • \$\begingroup\$ The mesh property holds the path to the mesh file as string and the VBO id. The module system is responsible for filling VBO which are zero with the meshes given by the path stored in the same property. Moreover, I haven't completely understand your answer. I got the idea so only use the entity property for real objects in the scene, but does this solve my problem? Is it that I don't need to add any additional entities at initialization time? I'll think about whether this is possible. \$\endgroup\$
    – danijar
    Commented Aug 11, 2013 at 12:13
  • \$\begingroup\$ If your remove any data which is only temporarily valid from your entities, there will be no mismatch. The entities should not contain any runtime dependent data (e.g. the window property). It should contain all the necessary data to recreate the runtime state. I don't know your system well enough to explain it any further. Sorry. \$\endgroup\$
    – Stephen
    Commented Aug 11, 2013 at 12:58
  • \$\begingroup\$ I understand that point. What I want is more added entities from database to the current running entity system than replacing the whole state. For example, I want to store the player and player created items in a different database than a level. Then load both into the system, so that the player remains even in a new level. Moreover, this is useful for streaming large landscapes. But if there is no other way, I may consider dropping this functionality. \$\endgroup\$
    – danijar
    Commented Aug 11, 2013 at 13:10
  • 1
    \$\begingroup\$ In which case you really need to follow and understand what @Stephen says, you save the instructions on how to recreate the entity and not the temporary states inside. At runtime you then replay these instructions. You must not even save temporary things like position because this changes in a new level... but only permanent features like inventory. The IDs are created fresh when your systems instantiate the entity. A database would not be appropriate, this would resemble a stream of instructions and not a static capture of state. \$\endgroup\$ Commented Aug 11, 2013 at 13:53
  • 1
    \$\begingroup\$ Okay, I think you are right. There is no way to allow import from multiple data source if I want to preserve entity ids. Moreover, I'll will not create entities on startup except something necessary is missing, like there is no entity with window or camera property. Right, I will continue storing the window as entity. ;-) \$\endgroup\$
    – danijar
    Commented Aug 11, 2013 at 15:27
4
\$\begingroup\$

Two pieces.

First, it's handy to have an idea of "archetypes." This is some data that defines a type of game object and which components it has and what their data is. For instance, you might have data file like:

Player:
  Sprite:
    image: character/players.png
  Physics:
    size: [2,2]
    center: [1,1]
  PlayerController
  AudioListener
  Health:
    max: 100
    current: 100
Enemy1:
  Sprite:
    image: character/enemy1.png
  Physics:
    size: [4,2]
    center: [2,1]
  AudioSource
  Brain:
    script: ai/enemy1.lua
  Health:
    max: 50
    current: 50
Enemy2:
  Sprite:
    image: character/enemy2.png
  Physics:
    size: [1,1]
    center: [0.5,0.5]
  AudioSource
  Brain:
    script: ai/enemy2.lua
  Health:
    max: 400
    current: 400

Assuming you track for each game object which archetype it was spawned from, you can save levels and save games as a unique ID and a list of data changes from the baseline. Exactly how you do that will vary a lot based on how you represent archetypes and inheritance of game object definitions.

It can seem like this is a class-based architecture, which it is to a degree, but maintains the flexibility of components and their data-driven potential. You can allow a form of "multiple inheritance" for archetypes if you want mixins and the like. Again, there's a bazillion ways to do this. You'll need to design a method that best fits your needs.

With this technique, you'll be default only be saving components that have changed in some way. Say your level has an instance of Enemy1 that has taken some damage. All you'd need to save is something like

object #7586 [Enemy1]:
  Health:
    current: 17

All the other components and fields are implicitly loaded as part of the Enemy1 definition. No need to save or load that data. Furthermore, since you aren't saving the whole object, the save can react to data definition changes. If you don't want that, just version you data (which you need to do in some capacity for saves anyway).

The second part then, which is fairly simple, is to just mark components or fields with extra metadata about whether they persist across save/load. If you mark the Health.max field as being non-persistant, then even if some effect modifies it for a particular enemy, the save file won't contain it. If the effects are saved/loaded and reapplied after load this is probably want you want.

You really want this kind of metadata markup anyway for networking, editors, and so on. You want to be able to mark up a field as being read-only so auto-generated editor GUIs don't let designers modify the field. You want to set which fields will be replicated over the network, and how (highly volatile data or semi-reliable data). Doing this in C++ is a complete pain in the rear, but it's trivial or even already partially built-in in many other languages.

\$\endgroup\$
10
  • \$\begingroup\$ Thanks for this addition, I really should consider that! However, my question was how to avoid id conflicts between entities loaded from the database and those already in the system. And as I understand, your approach haven't solved that. I provide another example, to make more clear what the point is. Say the application starts and and some entities are created during initialization. Then a level is loaded and adds entities from database which already have an id. What if some of those ids were already handed out during initialization? This is what I need to overcome. \$\endgroup\$
    – danijar
    Commented Aug 11, 2013 at 9:25
  • \$\begingroup\$ Why not just read all the IDs you already have and then start incrementing new entries from the newest one there? In general though you should have a method of reusing unused entries. Try look at the Artemis Framework for games. \$\endgroup\$
    – OmniOwl
    Commented Aug 11, 2013 at 9:41
  • \$\begingroup\$ @Vipar You're right, I should reuse free ids. But what if the id is used by some general initialization stuff and then the database tries to import an entity with exactly the same id? \$\endgroup\$
    – danijar
    Commented Aug 11, 2013 at 12:14
  • \$\begingroup\$ Then it is up to you, as the programmer of the system, to prevent that from happening. Giving out Unique ID's should be forced through the entire system so that no two entities can ever collide and have a duplicate ID entry. Reusing ID's that are not used any more is also key to great performance. When this is fully automated you are good :) \$\endgroup\$
    – OmniOwl
    Commented Aug 11, 2013 at 12:24
  • 1
    \$\begingroup\$ Use larger random IDs. If you randomly choose a 64-bit ID, you're relatively unlikely to have conflicts. If you're really paranoid or working with very large data sets, consider a v4 UUID. You can also use ID rewrites on load. Save out the ID of the entity, but when you load it, give it a new one, and store this in a map/dictionary. Then after loading all the data do a pass over loaded entities to rewrite references to the old IDs to the new ones. \$\endgroup\$ Commented Aug 11, 2013 at 18:54
1
\$\begingroup\$

You can split the ID integer space in Temporal IDs and Permanent IDs and treat it different. E.g: using a 16bits unsigned integer:

  • [0 - 32767] Permanent IDs
  • [32768 - 65535] Temporal IDs

This way a saved entity never will get a temporal ID and there will be no collision.

You can adjust the ranges knowing the amounts of entities of each type.

\$\endgroup\$
4
  • \$\begingroup\$ Or every id could consist of two parts. The first part is a hash of the database file name, and the second part is the normal id handed out by the manager. What do you think? \$\endgroup\$
    – danijar
    Commented Aug 12, 2013 at 9:44
  • \$\begingroup\$ sounds that it may work. Something like hash=0 for temporaries. \$\endgroup\$
    – Zhen
    Commented Aug 12, 2013 at 14:25
  • \$\begingroup\$ What would I need temporaries for? \$\endgroup\$
    – danijar
    Commented Aug 12, 2013 at 14:30
  • 1
    \$\begingroup\$ @danijar, for temporaries I mean entities that don't need to be saved on databases. \$\endgroup\$
    – Zhen
    Commented Aug 12, 2013 at 14:32

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .