# HG changeset patch # User Patric Stout # Date 2021-06-08 11:21:31 # Node ID 4919875a9031bf938ebf0553c15ffdc56295d774 # Parent 51d57b4c2615ea1607d3b433da58d591afdd73eb Codechange: ability to store structs and list of structs in savegames The commits following this will use this new functionality. Currently, a few places do this manually. This has as drawback that the Save() and Load() code need to be in sync, and that any change can result in (old) savegames no longer loading. In general, it is annoying code to maintain. By putting everything in a description table, and use that for both Save() and Load(), it becomes easier to see what is going on, and hopefully less likely for people to make mistakes. diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -1450,6 +1450,27 @@ size_t SlCalcObjMemberLength(const void case SL_WRITEBYTE: return 1; // a byte is logically of size 1 case SL_VEH_INCLUDE: return SlCalcObjLength(object, GetVehicleDescription(VEH_END)); case SL_ST_INCLUDE: return SlCalcObjLength(object, GetBaseStationDescription()); + case SL_STRUCT: + case SL_STRUCTLIST: { + if (!SlIsObjectValidInSavegame(sld)) break; + + NeedLength old_need_length = _sl.need_length; + size_t old_obj_len = _sl.obj_len; + + _sl.need_length = NL_CALCLENGTH; + _sl.obj_len = 0; + + /* Pretend that we are saving to collect the object size. Other + * means are difficult, as we don't know the length of the list we + * are about to store. */ + sld.handler->Save(const_cast(object)); + size_t length = _sl.obj_len; + + _sl.obj_len = old_obj_len; + _sl.need_length = old_need_length; + + return length; + } default: NOT_REACHED(); } return 0; @@ -1505,8 +1526,6 @@ size_t SlCalcObjMemberLength(const void static bool SlObjectMember(void *object, const SaveLoad &sld) { - void *ptr = GetVariableAddress(object, sld); - assert(IsVariableSizeRight(sld)); VarType conv = GB(sld.conv, 0, 8); @@ -1517,10 +1536,12 @@ static bool SlObjectMember(void *object, case SL_STR: case SL_REFLIST: case SL_DEQUE: - case SL_STDSTR: + case SL_STDSTR: { /* CONDITIONAL saveload types depend on the savegame version */ if (!SlIsObjectValidInSavegame(sld)) return false; + void *ptr = GetVariableAddress(object, sld); + switch (sld.cmd) { case SL_VAR: SlSaveLoadConv(ptr, conv); break; case SL_REF: SlSaveLoadRef(ptr, conv); break; @@ -1532,11 +1553,14 @@ static bool SlObjectMember(void *object, default: NOT_REACHED(); } break; + } /* SL_WRITEBYTE writes a value to the savegame to identify the type of an object. * When loading, the value is read explicitly with SlReadByte() to determine which * object description to use. */ - case SL_WRITEBYTE: + case SL_WRITEBYTE: { + void *ptr = GetVariableAddress(object, sld); + switch (_sl.action) { case SLA_SAVE: SlWriteByte(*(uint8 *)ptr); break; case SLA_LOAD_CHECK: @@ -1546,15 +1570,34 @@ static bool SlObjectMember(void *object, default: NOT_REACHED(); } break; + } /* SL_VEH_INCLUDE loads common code for vehicles */ - case SL_VEH_INCLUDE: + case SL_VEH_INCLUDE: { + void *ptr = GetVariableAddress(object, sld); SlObject(ptr, GetVehicleDescription(VEH_END)); break; - - case SL_ST_INCLUDE: + } + + case SL_ST_INCLUDE: { + void *ptr = GetVariableAddress(object, sld); SlObject(ptr, GetBaseStationDescription()); break; + } + + case SL_STRUCT: + case SL_STRUCTLIST: + if (!SlIsObjectValidInSavegame(sld)) return false; + + switch (_sl.action) { + case SLA_SAVE: sld.handler->Save(object); break; + case SLA_LOAD_CHECK: sld.handler->LoadCheck(object); break; + case SLA_LOAD: sld.handler->Load(object); break; + case SLA_PTRS: sld.handler->FixPointers(object); break; + case SLA_NULL: break; + default: NOT_REACHED(); + } + break; default: NOT_REACHED(); } diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -400,6 +400,73 @@ struct ChunkHandler { /** A table of ChunkHandler entries. */ using ChunkHandlerTable = span; +/** A table of SaveLoad entries. */ +using SaveLoadTable = span; + +/** Handler for saving/loading an object to/from disk. */ +class SaveLoadHandler { +public: + virtual ~SaveLoadHandler() {} + + /** + * Save the object to disk. + * @param object The object to store. + */ + virtual void Save(void *object) const {} + + /** + * Load the object from disk. + * @param object The object to load. + */ + virtual void Load(void *object) const {} + + /** + * Similar to load, but used only to validate savegames. + * @param object The object to load. + */ + virtual void LoadCheck(void *object) const {} + + /** + * A post-load callback to fix #SL_REF integers into pointers. + * @param object The object to fix. + */ + virtual void FixPointers(void *object) const {} + + /** + * Get the description of the fields in the savegame. + */ + virtual SaveLoadTable GetDescription() const = 0; +}; + +/** + * Default handler for saving/loading an object to/from disk. + * + * This handles a few common things for handlers, meaning the actual handler + * needs less code. + * + * Usage: class SlMine : public DefaultSaveLoadHandler {} + * + * @tparam TImpl The class initializing this template. + * @tparam TObject The class of the object using this SaveLoadHandler. + */ +template +class DefaultSaveLoadHandler : public SaveLoadHandler { +public: + SaveLoadTable GetDescription() const override { return static_cast(this)->description; } + + virtual void Save(TObject *object) const {} + void Save(void *object) const override { this->Save(static_cast(object)); } + + virtual void Load(TObject *object) const {} + void Load(void *object) const override { this->Load(static_cast(object)); } + + virtual void LoadCheck(TObject *object) const {} + void LoadCheck(void *object) const override { this->LoadCheck(static_cast(object)); } + + virtual void FixPointers(TObject *object) const {} + void FixPointers(void *object) const override { this->FixPointers(static_cast(object)); } +}; + /** Type of reference (#SLE_REF, #SLE_CONDREF). */ enum SLRefType { REF_ORDER = 0, ///< Load/save a reference to an order. @@ -501,10 +568,12 @@ enum SaveLoadType : byte { SL_REFLIST = 4, ///< Save/load a list of #SL_REF elements. SL_DEQUE = 5, ///< Save/load a deque of #SL_VAR elements. SL_STDSTR = 6, ///< Save/load a \c std::string. + SL_STRUCT = 7, ///< Save/load a struct. + SL_STRUCTLIST = 8, ///< Save/load a list of structs. /* non-normal save-load types */ - SL_WRITEBYTE = 8, - SL_VEH_INCLUDE = 9, - SL_ST_INCLUDE = 10, + SL_WRITEBYTE = 9, + SL_VEH_INCLUDE = 10, + SL_ST_INCLUDE = 11, }; typedef void *SaveLoadAddrProc(void *base, size_t extra); @@ -519,11 +588,9 @@ struct SaveLoad { size_t size; ///< the sizeof size. SaveLoadAddrProc *address_proc; ///< callback proc the get the actual variable address in memory size_t extra_data; ///< extra data for the callback proc + SaveLoadHandler *handler; ///< Custom handler for Save/Load procs. }; -/** A table of SaveLoad entries. */ -using SaveLoadTable = span; - /** * Storage of simple variables, references (pointers), and arrays. * @param cmd Load/save type. @see SaveLoadType @@ -535,7 +602,7 @@ using SaveLoadTable = span void * { assert(b != nullptr); return const_cast(static_cast(std::addressof(static_cast(b)->variable))); }, extra} +#define SLE_GENERAL(cmd, base, variable, type, length, from, to, extra) {cmd, type, length, from, to, cpp_sizeof(base, variable), [] (void *b, size_t) -> void * { assert(b != nullptr); return const_cast(static_cast(std::addressof(static_cast(b)->variable))); }, extra, nullptr} /** * Storage of a variable in some savegame versions. @@ -671,13 +738,13 @@ using SaveLoadTable = span void * { return static_cast(std::addressof(variable)); }, extra} +#define SLEG_GENERAL(cmd, variable, type, length, from, to, extra) {cmd, type, length, from, to, sizeof(variable), [] (void *, size_t) -> void * { return static_cast(std::addressof(variable)); }, extra, nullptr} /** * Storage of a global variable in some savegame versions. @@ -739,6 +806,14 @@ using SaveLoadTable = span GetSettings if (is_loading && (sd->flags & SF_NO_NETWORK_SYNC) && _networking && !_network_server) { /* We don't want to read this setting, so we do need to skip over it. */ - saveloads.push_back({sd->save.cmd, GetVarFileType(sd->save.conv) | SLE_VAR_NULL, sd->save.length, sd->save.version_from, sd->save.version_to, 0, nullptr, 0}); + saveloads.push_back({sd->save.cmd, GetVarFileType(sd->save.conv) | SLE_VAR_NULL, sd->save.length, sd->save.version_from, sd->save.version_to, 0, nullptr, 0, nullptr}); continue; }