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; }