# gamestate.h ## Overview
The Gamestate API provides a generic way of representing the "state" of a game, in such a way that it can be serialized to disk (for saved games or checkpoint features) or synchronized over the network (for multiplayer). The Gamestate API uses an abstract representation of state: * The state consists of a number of Objects. * Each Object is identified by an Id. * The state defines a number of Struct Types. * Each Struct Type has a fixed binary size and a number of Members. * A Member has a Name, a Type and an Offset into the struct's binary blob. * An Object can have a number of Structs. * Each Struct in the object is associated with a certain Struct Type. * Object structs are binary blobs that represent the value of each member. Note that the Gamestate does not make any assumption of what an Object *is*, and you can use Objects to represent many different kinds of things. An Object could be a player an item or something more abstract like a list of highscores. `entity.h` provides a default mechanism that synchronizes entities and components with Objects and Structs. But that is not the only way you can use the Gamestate API. You can also create your own representation of State and manually sync between the Gamestate representation and the world. The Gamestate reasons about the state in terms of *changes* from an initial state. I.e. suppose you have a level with 100,000 trees and the player chops down one of them. In this case, the Gamestate could just save that this particular tree was destroyed and ignore the other 99,999 trees which means that the saved file can be a lot smaller. Note that if you want to represent the full state in the Gamestate you could just have the initial state be empty with no objects in it. Note that reasoning about the Gamestate in terms of changes also works well for multiplayer. In order to synchronize the gamestate in a multiplayer setting, we can just transmit the changes to the game state between the players.
## Index
`struct tm_gamestate_object_id_t`
`struct tm_gamestate_struct_id_t`
`enum tm_gamestate_write_grouping`
`struct tm_gamestate_struct_t`

Member callbacks. These callbacks are called to inform the application about a change to a member
`struct tm_gamestate_float_member_t`
`struct tm_gamestate_double_member_t`
`struct tm_gamestate_u64_member_t`
`struct tm_gamestate_vec2_member_t`
`struct tm_gamestate_vec3_member_t`
`struct tm_gamestate_vec4_member_t`
`struct tm_gamestate_reference_member_t`
`struct tm_gamestate_custom_member_t`
`struct tm_gamestate_buffer_member_t`
`struct tm_gamestate_collection_member_t`
`enum tm_gamestate_member_type`
`struct tm_gamestate_member_t`
`struct tm_gamestate_persistent_buffer_t`
`struct tm_gamestate_object_configuration_t`
`struct tm_gamestate_dumped_changes_t`
`tm_gamestate_o`
`struct tm_gamestate_singleton_t`

`struct tm_gamestate_object_validator_i`
`name`
`created()`
`deleted()`

`tm_gamestate_object_validator_i_version`

`struct tm_gamestate_struct_validator_i`
`name`
`struct_hash`
`created()`
`changed()`
`deleted()`

`tm_gamestate_struct_validator_i_version`

`struct tm_gamestate_member_validator_i`
`name`
`struct_hash`
`member_name`
`changed()`

`tm_gamestate_member_validator_i_version`

`struct tm_gamestate_api`
`create()`
`destroy()`
`reset()`
`add_struct_type()`
`struct_global_members()`
`object_configuration()`
`struct_configuration()`
`configuration_relevant_object()`
`reserve_object_id()`
`create_object()`
`delete_object()`
`reserve_persistent_buffer()`
`create_struct()`
`delete_struct()`
`set_struct_raw()`
`float_member_set()`
`double_member_set()`
`u64_member_set()`
`vec2_member_set()`
`vec3_member_set()`
`vec4_member_set()`
`reference_member_set()`
`custom_member_set()`
`buffer_member_set()`
`collection_member_set()`
`create_new_frame()`
`compress_uncompressed_changes()`
`discard_uncompressed_changes()`
`dump_all()`
`load_all()`
`dump_uncompressed_changes()`
`load_uncompressed_changes()`
`ready_for_uncompressed_loading()`
`add_singleton()`
`deserialize_singleton()`
`add_object_validator()`
`add_struct_validator()`
`add_member_validator()`

`tm_gamestate_api_version`
## API
### `struct tm_gamestate_object_id_t`
ID that identifies a particular Object in the Gamestate. IDs are generated by the `create_object` API and are guaranteed to be unique. ~~~c typedef struct tm_gamestate_object_id_t { uint64_t u64; } tm_gamestate_object_id_t; ~~~
### `struct tm_gamestate_struct_id_t`
Id that identifies a particular Struct for a particular Object in the game state. !!! TODO: GAMESTATE-REVIEW It feels weird that this struct has two ways of identifying the Struct -- by (object, name) and by (index) -- I think this should be split up into two separate representations, because otherwise you can always have the problem that these two representations go out of sync. ~~~c typedef struct tm_gamestate_struct_id_t { // Object that owns the struct. tm_gamestate_object_id_t o; // Hashed name of the struct. tm_strhash_t s; // Index of the Struct's data into the array storing all the Struct data for the Struct Type. uint32_t index; TM_PAD(4); } tm_gamestate_struct_id_t; ~~~
### `enum tm_gamestate_write_grouping`
~~~c typedef enum tm_gamestate_write_grouping { TM_GAMESTATE_WRITE_NORMAL, TM_GAMESTATE_WRITE_SEPARATED, TM_GAMESTATE_WRITE_SINGULARLY, } tm_gamestate_write_grouping; ~~~
### `struct tm_gamestate_struct_t`
Defines a Struct Type in the Gamestate. ~~~c typedef struct tm_gamestate_struct_t { // Name of the struct. The name must be unique among all Struct Types in the Gamestate. const char *name; // Size in bytes of the Struct's binary data. uint32_t size; // Used to determine in which order Structs are restored when a saved state is loaded. Lower // values get restored first. float restore_sort_order; void *user_data; tm_gamestate_write_grouping grouping; TM_PAD(4); // Callback called when this Struct Type is created on an Object. `data` and `data_size` // represent the binary data for the struct. void (*created)(struct tm_gamestate_o *state, void *user_data, tm_strhash_t config_hash, tm_gamestate_struct_id_t s, void *data, uint32_t data_size); // Callback called when this Struct is changed. `data` and `data_size` // represent the binary data for the struct. If `data_size` is zero, it means the struct // has been cleared to zero. void (*changed)(struct tm_gamestate_o *state, void *user_data, tm_strhash_t config_hash, tm_gamestate_struct_id_t s, void *data, uint32_t data_size); // Callback called when this Struct Type is deleted on an Object. void (*deleted)(struct tm_gamestate_o *state, void *user_data, tm_strhash_t config_hash, tm_gamestate_struct_id_t s); // Callback called when [[discard_uncompressed_changes()]] is called and [[create_struct()]] was called in that same frame. // This is useful to notify the user that a struct hasn't actually been created on the gamestate. void (*discarded)(struct tm_gamestate_o *state, void *user_data, tm_gamestate_struct_id_t s); } tm_gamestate_struct_t ~~~

Member callbacks. These callbacks are called to inform the application about a change to a member

* GAMESTATE-REVIEW(Leonardo): Maybe we should pass the member name hash as well here? ### `struct tm_gamestate_float_member_t`
~~~c typedef struct tm_gamestate_float_member_t { void (*changed)(struct tm_gamestate_o *state, void *user_data, tm_gamestate_object_id_t o, tm_strhash_t struct_name, float value); } tm_gamestate_float_member_t; ~~~
### `struct tm_gamestate_double_member_t`
~~~c typedef struct tm_gamestate_double_member_t { void (*changed)(struct tm_gamestate_o *state, void *user_data, tm_gamestate_object_id_t o, tm_strhash_t struct_name, double value); } tm_gamestate_double_member_t; ~~~
### `struct tm_gamestate_u64_member_t`
~~~c typedef struct tm_gamestate_u64_member_t { void (*changed)(struct tm_gamestate_o *state, void *user_data, tm_gamestate_object_id_t o, tm_strhash_t struct_name, uint64_t value); } tm_gamestate_u64_member_t; ~~~
### `struct tm_gamestate_vec2_member_t`
~~~c typedef struct tm_gamestate_vec2_member_t { void (*changed)(struct tm_gamestate_o *state, void *user_data, tm_gamestate_object_id_t o, tm_strhash_t struct_name, tm_vec2_t value); } tm_gamestate_vec2_member_t; ~~~
### `struct tm_gamestate_vec3_member_t`
~~~c typedef struct tm_gamestate_vec3_member_t { void (*changed)(struct tm_gamestate_o *state, void *user_data, tm_gamestate_object_id_t o, tm_strhash_t struct_name, tm_vec3_t value); } tm_gamestate_vec3_member_t; ~~~
### `struct tm_gamestate_vec4_member_t`
~~~c typedef struct tm_gamestate_vec4_member_t { void (*changed)(struct tm_gamestate_o *state, void *user_data, tm_gamestate_object_id_t o, tm_strhash_t struct_name, tm_vec4_t value); } tm_gamestate_vec4_member_t; ~~~
### `struct tm_gamestate_reference_member_t`
~~~c typedef struct tm_gamestate_reference_member_t { void (*changed)(struct tm_gamestate_o *state, void *user_data, tm_gamestate_object_id_t o, tm_strhash_t struct_name, tm_gamestate_object_id_t value); } tm_gamestate_reference_member_t; ~~~
### `struct tm_gamestate_custom_member_t`
~~~c typedef struct tm_gamestate_custom_member_t { uint32_t size; TM_PAD(4); void (*changed)(struct tm_gamestate_o *state, void *user_data, tm_gamestate_object_id_t o, tm_strhash_t struct_name, void *data); } tm_gamestate_custom_member_t; ~~~
### `struct tm_gamestate_buffer_member_t`
~~~c typedef struct tm_gamestate_buffer_member_t { void (*changed)(struct tm_gamestate_o *state, void *user_data, tm_gamestate_object_id_t o, tm_strhash_t struct_name, void *data, uint32_t data_size); } tm_gamestate_buffer_member_t; ~~~
### `struct tm_gamestate_collection_member_t`
~~~c typedef struct tm_gamestate_collection_member_t { void (*changed)(struct tm_gamestate_o *state, void *user_data, tm_gamestate_object_id_t o, tm_strhash_t struct_name, tm_strhash_t identifier, void *data, uint32_t data_size); } tm_gamestate_collection_member_t; ~~~
### `enum tm_gamestate_member_type`
Possible Types for a Member. ~~~c typedef enum tm_gamestate_member_type { TM_GAMESTATE_MEMBER_TYPE__FLOAT, TM_GAMESTATE_MEMBER_TYPE__DOUBLE, TM_GAMESTATE_MEMBER_TYPE__U64, TM_GAMESTATE_MEMBER_TYPE__VEC2, TM_GAMESTATE_MEMBER_TYPE__VEC3, TM_GAMESTATE_MEMBER_TYPE__VEC4, TM_GAMESTATE_MEMBER_TYPE__OBJECT_REFERENCE, TM_GAMESTATE_MEMBER_TYPE__CUSTOM, TM_GAMESTATE_MEMBER_TYPE__BUFFER, TM_GAMESTATE_MEMBER_TYPE__COLLECTION, } tm_gamestate_member_type; ~~~
### `struct tm_gamestate_member_t`
Defines a Member in a Struct. (See `add_struct_type()`.) ~~~c typedef struct tm_gamestate_member_t { // Name of the member. const char *name; // Type of the member. tm_gamestate_member_type type; // Offset of the member in the struct. uint32_t offset; tm_gamestate_write_grouping grouping; TM_PAD(4); // Type specific member configuration. union { tm_gamestate_float_member_t f; tm_gamestate_double_member_t d; tm_gamestate_u64_member_t u64; tm_gamestate_vec2_member_t vec2; tm_gamestate_vec3_member_t vec3; tm_gamestate_vec4_member_t vec4; tm_gamestate_reference_member_t reference; tm_gamestate_custom_member_t custom; tm_gamestate_buffer_member_t buffer; tm_gamestate_collection_member_t collection; }; } tm_gamestate_member_t; ~~~
### `struct tm_gamestate_persistent_buffer_t`
Represents a Persistent Buffer in the Gamestate. Persistent Buffers can be used to store variable sized data. When you do this, you will use a Struct Member to hold a reference to the Persistent Buffer. ~~~c typedef struct tm_gamestate_persistent_buffer_t { // Size of the buffer. uint32_t size; TM_PAD(4); // Data for the buffer. void *data; } tm_gamestate_persistent_buffer_t; ~~~
### `struct tm_gamestate_object_configuration_t`
~~~c typedef struct tm_gamestate_object_configuration_t { void *user_data; void (*created)(struct tm_gamestate_o *gs, void *ud, tm_gamestate_object_id_t id); void (*deleted)(struct tm_gamestate_o *gs, void *ud, tm_gamestate_object_id_t id); } tm_gamestate_object_configuration_t; ~~~
### `struct tm_gamestate_dumped_changes_t`
~~~c typedef struct tm_gamestate_dumped_changes_t { bool has_timestamp; TM_PAD(3); uint32_t size; void *data; uint32_t num_dumped_members; TM_PAD(4); uint64_t *dumped_member_hash_keys; uint32_t num_origins; TM_PAD(4); void **origins; } tm_gamestate_dumped_changes_t; ~~~
### `tm_gamestate_o`
~~~c typedef struct tm_gamestate_o tm_gamestate_o; ~~~ Object that represents the Gamestate.
### `struct tm_gamestate_singleton_t`
~~~c typedef struct tm_gamestate_singleton_t { const char *name; uint32_t size; TM_PAD(4); void (*serialize)(void *source, void *dest); void (*deserialize)(void *dest, void *source); } tm_gamestate_singleton_t; ~~~
### `struct tm_gamestate_object_validator_i`
#### `name` ~~~c const char *name; ~~~ #### `created()` ~~~c bool (*created)(struct tm_gamestate_o *gs, void *ud, tm_gamestate_object_id_t id); ~~~ #### `deleted()` ~~~c bool (*deleted)(struct tm_gamestate_o *gs, void *ud, tm_gamestate_object_id_t id); ~~~
### `tm_gamestate_object_validator_i_version`
~~~c #define tm_gamestate_object_validator_i_version TM_VERSION(0, 0, 1) ~~~
### `struct tm_gamestate_struct_validator_i`
#### `name` ~~~c const char *name; ~~~ #### `struct_hash` ~~~c tm_strhash_t struct_hash; ~~~ #### `created()` ~~~c bool (*created)(struct tm_gamestate_o *state, void *user_data, tm_gamestate_struct_id_t s, void *data, uint32_t data_size); ~~~ #### `changed()` ~~~c bool (*changed)(struct tm_gamestate_o *state, void *user_data, tm_gamestate_struct_id_t s, void *data, uint32_t data_size); ~~~ #### `deleted()` ~~~c bool (*deleted)(struct tm_gamestate_o *state, void *user_data, tm_gamestate_struct_id_t s); ~~~
### `tm_gamestate_struct_validator_i_version`
~~~c #define tm_gamestate_struct_validator_i_version TM_VERSION(0, 0, 1) ~~~
### `struct tm_gamestate_member_validator_i`
#### `name` ~~~c const char *name; ~~~ #### `struct_hash` ~~~c tm_strhash_t struct_hash; ~~~ #### `member_name` ~~~c const char *member_name; ~~~ #### `changed()` ~~~c bool (*changed)(struct tm_gamestate_o *state, void *user_data, tm_gamestate_struct_id_t s, tm_strhash_t identifier, void *data, uint32_t data_size); ~~~
### `tm_gamestate_member_validator_i_version`
~~~c #define tm_gamestate_member_validator_i_version TM_VERSION(0, 0, 2) ~~~
### `struct tm_gamestate_api`
API for saving and restoring the Gamestate. There are three main phases: 1. Configuration phase, where the Struct/Member layouts are declared 2. Runtime phase, where the Gamestate is notified of application state changes. 3. Save/Load phase, where the Gamestate is serialized to or deserialized from a buffer. #### `create()` ~~~c struct tm_gamestate_o *(*create)(struct tm_allocator_i *a); ~~~ Creates a Gamestate. #### `destroy()` ~~~c void (*destroy)(struct tm_gamestate_o *gamestate); ~~~ Destroys a Gamestate created by `create()`. #### `reset()` ~~~c void (*reset)(struct tm_gamestate_o *gamestate); ~~~ Completely clears the Gamestate and starts anew. Important: all the changes will be lost. #### `add_struct_type()` ~~~c void (*add_struct_type)(struct tm_gamestate_o *gamestate, tm_gamestate_struct_t s); ~~~ Adds a Struct Type to the Gamestate. #### `struct_global_members()` ~~~c void (*struct_global_members)(struct tm_gamestate_o *gamestate, tm_strhash_t struct_hash, tm_gamestate_member_t *m, uint32_t member_count); ~~~ #### `object_configuration()` ~~~c void (*object_configuration)(struct tm_gamestate_o *gamestate, tm_strhash_t config, tm_gamestate_object_configuration_t oc); ~~~ #### `struct_configuration()` ~~~c void (*struct_configuration)(struct tm_gamestate_o *gamestate, tm_strhash_t config, tm_strhash_t struct_hash, tm_gamestate_member_t *m, uint32_t member_count); ~~~ #### `configuration_relevant_object()` ~~~c void (*configuration_relevant_object)(struct tm_gamestate_o *gamestate, tm_gamestate_object_id_t id, tm_strhash_t dump_config_hash); ~~~ #### `reserve_object_id()` ~~~c tm_gamestate_object_id_t (*reserve_object_id)(struct tm_gamestate_o *gamestate); ~~~ Reserves a persistent_id on the Gamestate, without creating the object. Returns the persistent_id reserved on the Gamestate. #### `create_object()` ~~~c tm_gamestate_object_id_t (*create_object)(struct tm_gamestate_o *gamestate); ~~~ Notifies the Gamestate that an object was created in the application. Returns the persistent_id of the newly created object. #### `delete_object()` ~~~c void (*delete_object)(struct tm_gamestate_o *gamestate, tm_gamestate_object_id_t o); ~~~ Notifies the Gamestate that an object was destroyed in the application. #### `reserve_persistent_buffer()` ~~~c tm_gamestate_persistent_buffer_t (*reserve_persistent_buffer)(struct tm_gamestate_o *gamestate, tm_strhash_t struct_name, const char *member_name, uint32_t size); ~~~ Allocates a persistent buffer of the specified `size`, to be used for dynamically sized member data. The buffer should be stored as a Struct Member in an object, so that the Gamestate can reason about when the buffer can be freed. You need to pass the `struct_name` and the `member_name` so that the Gamestate can check that you correctly defined the member. #### `create_struct()` ~~~c tm_gamestate_struct_id_t (*create_struct)(struct tm_gamestate_o *gamestate, tm_gamestate_object_id_t o, tm_strhash_t struct_name, void *data, uint32_t size); ~~~ Creates the struct `struct_name` on the Gamestate Object `o`. `data` is the content of the struct. The `size` specifies the size of the data and must match the size of the Struct's binary data. #### `delete_struct()` ~~~c void (*delete_struct)(struct tm_gamestate_o *gamestate, tm_gamestate_struct_id_t id); ~~~ Deletes the struct `id` from the Gamestate. #### `set_struct_raw()` ~~~c void (*set_struct_raw)(struct tm_gamestate_o *gamestate, tm_gamestate_struct_id_t id, void *data, uint32_t size); ~~~ Notify to the Gamestate that the _entire_ struct `id` has changed. You should use this only with id's created with the create_struct API. (Because they have been copied on the Gamestate). `data` is a pointer to the new data, and size must be equal to the specified size in the _configure_struct_type_ API for this to work correctly. You should only call this if you know very well what you're doing, as in most cases you'd better call the member changed APIs for every member of your struct that changed. #### `float_member_set()` ~~~c void (*float_member_set)(struct tm_gamestate_o *gamestate, tm_gamestate_struct_id_t s, const char *member_name, float value); ~~~ APIs to notify the Gamestate about members that changed in the application state. TODO(Leonardo): take string hashes instead of strings here. #### `double_member_set()` ~~~c void (*double_member_set)(struct tm_gamestate_o *gamestate, tm_gamestate_struct_id_t s, const char *member_name, double value); ~~~ #### `u64_member_set()` ~~~c void (*u64_member_set)(struct tm_gamestate_o *gamestate, tm_gamestate_struct_id_t s, const char *member_name, uint64_t value); ~~~ #### `vec2_member_set()` ~~~c void (*vec2_member_set)(struct tm_gamestate_o *gamestate, tm_gamestate_struct_id_t s, const char *member_name, tm_vec2_t value); ~~~ #### `vec3_member_set()` ~~~c void (*vec3_member_set)(struct tm_gamestate_o *gamestate, tm_gamestate_struct_id_t s, const char *member_name, tm_vec3_t value); ~~~ #### `vec4_member_set()` ~~~c void (*vec4_member_set)(struct tm_gamestate_o *gamestate, tm_gamestate_struct_id_t s, const char *member_name, tm_vec4_t value); ~~~ #### `reference_member_set()` ~~~c void (*reference_member_set)(struct tm_gamestate_o *gamestate, tm_gamestate_struct_id_t s, const char *member_name, tm_gamestate_object_id_t value); ~~~ #### `custom_member_set()` ~~~c void (*custom_member_set)(struct tm_gamestate_o *gamestate, tm_gamestate_struct_id_t s, const char *member_name, void *data, uint32_t size); ~~~ #### `buffer_member_set()` ~~~c void (*buffer_member_set)(struct tm_gamestate_o *gamestate, tm_gamestate_struct_id_t s, const char *member_name, tm_gamestate_persistent_buffer_t buffer); ~~~ #### `collection_member_set()` ~~~c void (*collection_member_set)(struct tm_gamestate_o *gamestate, tm_gamestate_struct_id_t s, const char *member_name, const char *identifier, void *data, uint32_t size); ~~~ #### `create_new_frame()` ~~~c void (*create_new_frame)(struct tm_gamestate_o *gamestate); ~~~ Create a new empty frame on the Gamestate. All the changes pushed after this call will be associated to the new frame. #### `compress_uncompressed_changes()` ~~~c struct tm_gamestate_dumped_changes_t *(*compress_uncompressed_changes)(struct tm_gamestate_o *gamestate, tm_strhash_t dump_config_hash, struct tm_allocator_i *a); ~~~ Compress all the changes that were pushed to the Gamestate in a single Frame that contains all the changes pushed up to this point. This is not called automatically by "create_new_frame" as some applications may want to explicitly access these uncompressed changes. if a valid allocator is passed, the changes that are compressed will be dumped accordingly to the configuration specified in the `dump_config_hash` parameter. #### `discard_uncompressed_changes()` ~~~c void (*discard_uncompressed_changes)(struct tm_gamestate_o *gamestate); ~~~ Ignores and deletes all the changes that were pushed to the Gamestate and _not_ compressed up to this point. Note that this call won't delete any change that has already been compressed. #### `dump_all()` ~~~c uint8_t *(*dump_all)(struct tm_gamestate_o *gamestate, tm_strhash_t config, uint32_t *written, struct tm_allocator_i *a); ~~~ Dump the entire Gamestate to a buffer using the allocator `a` and writes the written size to the `written` output parameter. Returns the buffer. #### `load_all()` ~~~c void (*load_all)(struct tm_gamestate_o *gamestate, tm_strhash_t config, uint8_t *state, uint32_t size, bool push_commands); ~~~ Resets the Gamestate and Start Loading the application state using the `state` parameter. #### `dump_uncompressed_changes()` ~~~c tm_gamestate_dumped_changes_t *(*dump_uncompressed_changes)(struct tm_gamestate_o *gamestate, tm_strhash_t config, struct tm_allocator_i *a); ~~~ #### `load_uncompressed_changes()` ~~~c void (*load_uncompressed_changes)(struct tm_gamestate_o *gamestate, tm_strhash_t config, uint8_t *changes, uint32_t size, bool push_commands, void *origin); ~~~ #### `ready_for_uncompressed_loading()` ~~~c void (*ready_for_uncompressed_loading)(struct tm_gamestate_o *gamestate, tm_gamestate_object_id_t id); ~~~ #### `add_singleton()` ~~~c void (*add_singleton)(struct tm_gamestate_o *gamestate, tm_gamestate_singleton_t s, void *ptr); ~~~ #### `deserialize_singleton()` ~~~c bool (*deserialize_singleton)(struct tm_gamestate_o *gamestate, const char *name, void *ptr); ~~~ #### `add_object_validator()` ~~~c void (*add_object_validator)(struct tm_gamestate_o *gamestate, void *user_data, tm_strhash_t config_hash, tm_gamestate_object_validator_i *validator); ~~~ #### `add_struct_validator()` ~~~c void (*add_struct_validator)(struct tm_gamestate_o *gamestate, void *user_data, tm_strhash_t config_hash, tm_gamestate_struct_validator_i *validator); ~~~ #### `add_member_validator()` ~~~c void (*add_member_validator)(struct tm_gamestate_o *gamestate, void *user_data, tm_strhash_t config_hash, tm_gamestate_member_validator_i *validator); ~~~
### `tm_gamestate_api_version`
~~~c #define tm_gamestate_api_version TM_VERSION(0, 0, 6) ~~~