Be able to carry out uplifts during minor release lifetimes. Document a way to be able to uplift struct content without incrementing EDITLEVEL and breaking existing savefiles. Use the mechanics outlined to uplift the contents instead, where it is feasible to do so. The uplift is currently one-way only. An uplifted savefile cannot be used with an earlier build of NetHack , one built with a lower SAVEFILE_REVISION_LEVEL, than the one which wrote the savefile. The final byte (byte 79) of the 80 critical bytes in the savefile, of which 10 are reserved for future expansion and not currently used, will now be used for holding the savefile revision level (SAVEFILE_REVISION_LEVEL in include/patchlevel.h) at the time the savefile was written. That leaves 9 of the bytes available for future use.
229 lines
8.3 KiB
C
229 lines
8.3 KiB
C
/* NetHack 5.0 revision.h $NHDT-Date: 1706213796 2024/01/25 20:16:36 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.116 $ */
|
|
/* Copyright (c) Michael Allison, 2026. */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
/*
|
|
* Supporting revisions to NetHack structs via incemental
|
|
* uplifts, rather than incrementing EDITLEVEL and breaking
|
|
* savefile compatibility.
|
|
*
|
|
* Here is the contrived example struct we'll use for
|
|
* this document:
|
|
*
|
|
* The old revision: The new revision:
|
|
*
|
|
* struct mystruct { struct mystruct {
|
|
* int field1; int field1;
|
|
* int field2; int field2;
|
|
* char field3; char field3;
|
|
* long field4; long field4;
|
|
* }; int newfielda;
|
|
* int newfieldb;
|
|
* };
|
|
*
|
|
* Steps (using mystruct as an example name)
|
|
*
|
|
* 1. Paste an exact copy of the struct declaration as it is/was
|
|
* in the previous revision into include/revision.h, shrouded by
|
|
* #if defined(MYSTRUCT_REV0), and tack a '_rev0' suffix onto the
|
|
* name of the struct. For example,
|
|
*
|
|
* #if defined(MYSTRUCT_REV0)
|
|
* struct mystruct_rev0 {
|
|
* int field1;
|
|
* int field2;
|
|
* char field3;
|
|
* long field4;
|
|
* };
|
|
*
|
|
* That goes just ahead of this existing placeholder:
|
|
*
|
|
* #elif defined(XXX_REV0)
|
|
*
|
|
* 2. Place a comment ahead of the mystruct_rev0 declaration
|
|
* that explains what has changed, and why the revision
|
|
* is required.
|
|
*
|
|
* 3. Immediately following the revised struct declaration in the
|
|
* original header file where it is located, add the following:
|
|
*
|
|
* #define MYSTRUCT_REV0
|
|
* #include "revision.h"
|
|
* #undef MYSTRUCT_REV0
|
|
*
|
|
* 4. In include/savefile.h, add a new prototype for a function
|
|
* to read the previous revision from the savefile, ideally
|
|
* immediately following the prototype for the current active
|
|
* one that hasn't got the '_rev0' suffix:
|
|
*
|
|
* extern void sfi_mystruct(NHFILE *, struct mystruct *,
|
|
* const char *);
|
|
* extern void sfi_mystruct_rev0(NHFILE *, struct mystruct_rev0 *,
|
|
* const char *);
|
|
*
|
|
* 5. Also in include/savefile.h, in the '#if NH_C < 202300L' block,
|
|
* add an entry below the one that should already exist for the
|
|
* struct that hasn't got the '_rev0' suffix:
|
|
*
|
|
* #define Sfi_mystruct(a,b,c) sfi_rm(a, b, c)
|
|
* #define Sfi_mystruct_rev0(a, b, c) sfi_mystruct_rev0(a, b, c)
|
|
*
|
|
* 6. Also in include/savefile.h, in the '#define sfi(nhfp, dt, tag)'
|
|
* generic function definition, add an entry below the one
|
|
* that should already exist for the struct name that hasn't
|
|
* got the '_rev0' suffix:
|
|
*
|
|
* struct mystruct * : sfi_rm, \
|
|
* struct mystruct_rev0 * : sfi_rm_rev0, \
|
|
*
|
|
* 7. Also in include/savefile.h, a little further down in the
|
|
* 'Sfi_' macro defininitions, add one below the existing
|
|
* one for the struct without the '_rev0' suffix:
|
|
*
|
|
* #define Sfi_mystruct(a,b,c) sfi(a, b, c)
|
|
* #define Sfi_mystruct_rev0(a, b, c) sfi(a, b, c)
|
|
*
|
|
* Only the input 'Sfi_' is required, because the old revision
|
|
* will never be written out to a file, only read in from a
|
|
* a file, so no 'Sfo_' is needed.
|
|
*
|
|
* 8. In include/sfmacros.h, add a new entry below the existing
|
|
* entry that exists without the '_rev0' suffix:
|
|
*
|
|
* SF_C(struct, mystruct)
|
|
* SF_C(struct, mystruct_rev0)
|
|
*
|
|
* 9. In include/sfprocs.h, add a new SF_PROTO_C entry below the
|
|
* existing entry that already exists without the '_rev0' suffix:
|
|
*
|
|
* SF_PROTO_C(struct, mystruct);
|
|
* SF_PROTO_C(struct, mystruct_rev0);
|
|
*
|
|
* 10. Also in include/sfprocs.h, add a new SF_ENTRY_C entry below the
|
|
* entry that already exists without the '_rev0' suffix:
|
|
*
|
|
* SF_ENTRY_C(struct, mystruct);
|
|
* SF_ENTRY_C(struct, mystruct_rev0);
|
|
*
|
|
* 11. In src/sfbase.c, add a prototype for a 'norm_ptrs' stub function
|
|
* below the entry that already exists without the '_rev0' suffix:
|
|
*
|
|
* void norm_ptrs_mystruct(struct mystruct *d_mystruct);
|
|
* void norm_ptrs_mystruct_rev0(struct mystruct_rev0 *d_mystruct);
|
|
*
|
|
* 12. Also in src/sfbase.c, add a 'norm_ptrs' stub function below the
|
|
* stub function that already exists without the '_rev0' suffix:
|
|
*
|
|
* void
|
|
* norm_ptrs_mystruct(struct mystruct *d_mystruct UNUSED)
|
|
* {
|
|
* }
|
|
*
|
|
* void
|
|
* norm_ptrs_mystruct_rev0(struct mystruct_rev0 *d_mystruct_rev0 UNUSED)
|
|
* {
|
|
* }
|
|
*
|
|
* 13. In src/sfstruct.c, add entries to the 'historical' section below the
|
|
* existing entry that doesn't have a '_rev0' suffix. You need an 'sfo_'
|
|
* entry and an 'sfi_' entry here, because they have to match function
|
|
* pointers in another struct. The 'sfo_' function will not be called
|
|
* from anywhere.
|
|
*
|
|
* historical_sfo_rm,
|
|
* historical_sfo_rm_rev0,
|
|
* ...
|
|
* historical_sfi_rm,
|
|
* historical_sfi_rm_rev0,
|
|
*
|
|
* 14. In the C source file where the 'Sfi_' call is made for your struct,
|
|
* the current code likely has a line for reading the struct from
|
|
* a file, similar the one shown below for the mystruct example:
|
|
*
|
|
* Sfi_mystruct(nhfp, &svl.mystruct, "mystruct-example");
|
|
*
|
|
* That single line will need to modified to become a code block similar
|
|
* to this, so that the uplift function will get called:
|
|
*
|
|
* if (!gu.uplift_needed_rev0_to_rev1) {
|
|
* Sfi_mystruct(nhfp, &svl.mystruct, "mystruct-example");
|
|
* } else {
|
|
* struct mystruct_rev0 old_mystruct;
|
|
*
|
|
* Sfi_mystruct_rev0(nhfp, &old_mystruct, "mystruct-example");
|
|
* uplift_mystruct_rev0_to_mystruct(&old_mystruct, &svl.mystruct);
|
|
* }
|
|
* }
|
|
*
|
|
* 15. Shortly after the reading of the struct by the 'Sfi_' function,
|
|
* if the newer revision of the struct added some new fields, new code
|
|
* will be needed to initialize the new fields to sane values.
|
|
* There is no data for the new fields in the exising savefile.
|
|
*
|
|
* if (gu.uplift_needed_rev0_to_rev1 == 1) {
|
|
* svl.mystruct.newfielda = sanevalue1;
|
|
* svl.mystruct.newfieldb = sanevalue2;
|
|
* }
|
|
*
|
|
* 16. In include/extern.h, add a prototype to the ' ### revision.c ###'
|
|
* section for the supporting uplift function:
|
|
*
|
|
* extern void uplift_mystruct_rev0_to_mystruct(struct mystruct_rev0 *,
|
|
* struct mystruct *);
|
|
*
|
|
* 17. Add the uplift function to src/revision.c. It needs to do
|
|
* field-by-field copies of the fields that exist in the old
|
|
* revision and the new revision. For now, it likely needs to
|
|
* be handcrafted using your knowledge of the struct's old
|
|
* and new revisions.
|
|
*
|
|
* Be sure that any new fields present in the newer revision
|
|
* of the struct get set to sane values or initialized to
|
|
* zero. Do whatever suits those fields best, based on your
|
|
* knowledge of the struct.
|
|
*
|
|
* void
|
|
* uplift_mystruct_rev0_to_mystruct(struct mystruct_rev0 *rev0,
|
|
* struct mystruct *rev1)
|
|
* {
|
|
* rev1->field1 = rev0->field1;
|
|
* rev1->field2 = rev0->field2;
|
|
* rev1->field3 = rev0->field3;
|
|
* rev1->field4 = rev0->field4;
|
|
*
|
|
* rev1->newfielda = 0; // new field
|
|
* rev1->newfieldb = 42; // new field
|
|
* }
|
|
*
|
|
*/
|
|
|
|
#if defined(MYSTRUCT_REV0)
|
|
#ifdef DEMO_UPLIFTS
|
|
/*
|
|
* struct mystruct_rev0
|
|
*
|
|
* This is the predecessor for 'struct mystruct'
|
|
* Revisions to 'struct mystruct' that 'struct mystruct_rev0' does not have:
|
|
* int newfielda;
|
|
* int newfieldb;
|
|
*
|
|
* The uplift function is:
|
|
* void uplift_mystruct_rev0_to_mystruct(struct *mystruct_rev0,
|
|
* struct *mystruct);
|
|
*/
|
|
|
|
struct mystruct_rev0 {
|
|
int field1;
|
|
int field2;
|
|
char field3;
|
|
long field4;
|
|
};
|
|
#endif /* DEMO_UPLIFTS */
|
|
|
|
#elif defined(XXX_REV0)
|
|
|
|
#else
|
|
#error Unproductive inclusion of revision.h
|
|
#endif
|
|
/* revision.h */
|