Files
Aurora.3/code/controllers/subsystems/persistence/persistence_objects.dm
FabianK3andGitHub 65a9f06765 Persistence Panel (#22979)
# Summary

This PR adds a new admin panel for persistence.

## Contents

- The panel features information about the current round and detailed
tracking of objects, records and generics.
- The previously introduced toggle-persistence verb has been removed and
integrated into the new panel.
- The panel can be seen by admins, mods and devs, but the
toggle-persistence is still locked to admin permissions only.

## Preview

The images are WIP and subject to change.

<img width="918" height="229" alt="image"
src="https://github.com/user-attachments/assets/16d3d73d-32e6-4907-bdff-d96699c27d49"
/>

<img width="919" height="253" alt="image"
src="https://github.com/user-attachments/assets/92e7f4d9-ccd4-4e79-949d-b8a35039da01"
/>

<img width="923" height="285" alt="image"
src="https://github.com/user-attachments/assets/e3796504-4cc6-4fce-b5f1-4df3486bfbee"
/>

<img width="923" height="270" alt="image"
src="https://github.com/user-attachments/assets/9fbb185d-181a-40ec-b89b-58eafcd47e4d"
/>
2026-08-08 20:47:55 +00:00

135 lines
6.3 KiB
Plaintext

/**
* Initializes persistent objects.
* This includes cleaning up expired objects from the database and instanciating all active tracks.
*/
/datum/controller/subsystem/persistence/proc/objectsInitialize()
PRIVATE_PROC(TRUE)
object_track_register = list()
if(SSatlas.current_map.path != "sccv_horizon") // The persistence system only supports objects from the main map levels for multiple reasons, e.g. Z level value, mapping support
log_subsystem_persistence_info("Persistent objects: Current map did not match SCCV Horizon, skipping persistent object initialization.")
return
// Delete all persistent objects in the database that have expired and have passed the cleanup grace period (PERSISTENT_EXPIRATION_CLEANUP_DELAY_DAYS)
objectsDatabaseCleanEntries()
// Retrieve all persistent data that is not expired
var/list/persistent_data = objectsDatabaseGetActiveEntries()
log_subsystem_persistence_info("Persistent objects: Retrieved [length(persistent_data)] entries for instancing this round.")
// Instantiate all remaining entries based of their type
// Assign persistence related vars found in /obj, apply content and add to live tracking list.
for (var/data in persistent_data)
CHECK_TICK
var/typepath = text2path(data["type"])
if (!ispath(typepath)) // Type checking
continue
// Note that the object here is instantiated without init args.
// Objects that require init args should fall back to INITALIZE_HINT_LATELOAD during Init,
// as this will give the subsystem the chance to apply the content and
// the object to continue with init logic after the subsystem is done in LateInitialize.
var/obj/instance = new typepath()
instance.persistent_objects_track_id = data["id"]
instance.persistent_objects_created_at = data["created_at"]
instance.persistent_objects_expires_at = data["expires_at"]
objectsApplyTrackContent(instance, data["content"], data["x"], data["y"], data["z"])
objectsRegisterTrack(instance, data["author_ckey"])
/**
* Finalize persistent object tracking.
* Adds new persistent objects, removes no longer existing persistent objects and updates changed persistent objects in the database.
*/
/datum/controller/subsystem/persistence/proc/objectsFinalize()
PRIVATE_PROC(TRUE)
if(SSatlas.current_map.path != "sccv_horizon") // The persistence system only supports objects from the main map levels for multiple reasons, e.g. Z level value, mapping support
log_subsystem_persistence_info("Persistent objects: Current map did not match SCCV Horizon, skipping persistent object finalization.")
if(length(object_track_register) > 0)
log_subsystem_persistence_warning("Persistent objects: There are [length(object_track_register)] tracked objects at finalization, while the map is not supported! These track will not be saved! Verify that SSatlas.current_map.path has not changed during the round!")
return
// Subsystem shutdown:
// Create new persistent records for objects that have been created in the round
// Update tracked objects that have an ID (already existing from previous rounds)
// Delete persistent records that no longer exist in the registry (removed during the round)
// Run checks on each track that might prevent further persistence
for (var/obj/track in object_track_register)
CHECK_TICK
var/turf/T = get_turf(track)
if(!T || !is_station_level(T.z)) // The persistence system only supports objects from the main map levels for multiple reasons, e.g. Z level value, mapping support
objectsDeregisterTrack(track)
var/created = 0
var/updated = 0
var/expired = 0
// Get already stored data before saving new tracks so we can compare what has been updated or removed during the round.
var/list/existing_data = objectsDatabaseGetActiveEntries()
for (var/obj/track in object_track_register)
CHECK_TICK
if (track.persistent_objects_track_id == 0)
// Tracked object has no ID meaning it is new, create a new persistent record for it
objectsDatabaseAddEntry(track)
created++
// Find tracks that have been removed during the round by trying to find the track by database ID
// If we find the track, we need to check if it requires an update instead
for (var/record in existing_data)
var/found = FALSE
for (var/obj/track in object_track_register)
CHECK_TICK
if (record["id"] == track.persistent_objects_track_id)
// A track with the same ID has been found in the register, it still exists, check if we need to update it instead
found = TRUE // Prevent expiration of track
var/changed = FALSE
var/turf/T = get_turf(track)
if (T && T.x != record["x"])
changed = TRUE
else if (T && T.y != record["y"])
changed = TRUE
else if (T && T.z != record["z"])
changed = TRUE
else if (objectsGetTrackContent(track) != record["content"])
changed = TRUE
if (changed)
objectsDatabaseUpdateEntry(track)
updated++
break // Track found (and perhaps updated), break off loop search as it won't need to be deleted anyways
if (!found)
// No track with the same ID has been found in the register, remove it from the database (expire)
objectsDatabaseExpireEntry(record["id"])
expired++
log_subsystem_persistence_info("Persistent objects: Created [created], updated [updated] and expired [expired] tracks.")
/**
* Safely get JSON persistent content of track.
* RETURN: JSON formatted content of track or null if an exception occured.
*/
/datum/controller/subsystem/persistence/proc/objectsGetTrackContent(obj/track)
PRIVATE_PROC(TRUE)
var/result = json_encode(list())
try
var/list/content = track.persistent_objects_get_content()
if(length(content))
result = json_encode(content)
catch(var/exception/e)
log_subsystem_persistence_error("Error during json serialization or retrieval of content for persistent object. Type: [track.type]", e)
return result
/**
* Safely apply persistent content to track.
* PARAMS:
* track = Object to apply content to.
* json = Custom persistent content JSON to be applied.
* x,y,z = x-y-z coordinates of object, can be null.
*/
/datum/controller/subsystem/persistence/proc/objectsApplyTrackContent(obj/track, json, x, y, z)
PRIVATE_PROC(TRUE)
try
track.persistent_objects_apply_content(json_decode(json), x, y, z)
catch(var/exception/e)
log_subsystem_persistence_error("Error during json deserialization or applying content for persistent object. Type: [track.type]", e)