The four rules
1
Capture from the record, not from the world
Reading the world captures values that are already doomed, and restoring them looks like a
success while changing nothing durable.It also avoids a subtler class of bug: a value that the live entity reports differently
from the way it is stored. Blueprints’ PermaProps adapter reads the frozen flag from the
record, because a frozen saved prop usually has no physics object and the live answer to
“is this frozen” is
false for exactly the props that are.2
Write the record first, the entity second
The record is what survives a restart. Order every operation that way:
3
Read back before you report success
A
WHERE clause that matched nothing is a silent success in most databases. Report
success only after reading the record back and comparing it.4
Compensate if the second half fails
Write the record back to its previous bytes, re-insert it with its original id, or
delete it again.If a compensating write itself fails, the operation ends half applied. Say so, and name
which half stands. Do not report “rolled back” when an artifact remains.
Report success only if the entity did too
For aproperties operation, put the live entity back as well when you compensate. A rollback
that restores only the record leaves the entity and the record disagreeing until the next map
load silently resolves it, and in the meantime the operator sees something that matches
neither.
Detect the host system properly
Prove, on everyAvailable call, what you actually depend on:
- the exact functions you will call are functions;
- the storage backend is one you support;
- the table exists;
- the schema is exactly what you expect.
NOT NULL column with no default would make every insert fail halfway.
Anything that fails is reported unavailable, with a sentence, and your domain is then absent
from the Version. That is the difference between “this Version knows nothing about your
domain” and “this Version asserts your domain is empty”, and the second one, restored, deletes
it all.
Keep it cheap: Available runs before every capture.
Say what you do not manage
Most host systems persist more than you are willing to manage. Say so, count it, and leave it alone. Blueprints’ PermaProps adapter manages props. PermaProps also persists vehicles, lamps, ragdolls, ammo crates, text screens and every NPC class. Those are left unmanaged and counted, never coerced into something they are not, and the count is logged and shown. Half-managing a category is worse than not managing it. A record of a class you have no model for, forced into your object shape, is a record a restore will act on.One object, one owner
If your addon claims an entity, no other adapter should be capturing or writing it. The way Blueprints does this internally is worth copying: the exclusion lives in the shared resolution path, so both the capture path and every write path close at once, rather than in each adapter separately where one of them will eventually be missed. It fails closed by name: an entity flagged by something else calling itself the same thing is still an entity somebody else believes they own.Portable identity is not portable
A database primary key is genuinelypersistent on the server that issued it, and means
nothing anywhere else. Another server’s database allocates from its own counter, so the same
number names a different object there.
Declare persistent (it is true, here), and never send an id across a server boundary
yourself. Blueprints’ deployment mapping handles the crossing, and it works precisely because
your Materialize returns a new local identity rather than echoing the source’s.