Persistence code refactoring (#22044)

# Summary

This PR refactores code and structure of the current persistence
subsystem, in preparation for Persistency: Circles, Squares and
Triangles.

This should not contain any user facing changes.

~~This PR is based of branch #21925 and **needs** to be merged in order.
The files changed display will be meaningless until then.~~ *Done.*

## Changes

- Split subsystem in partial files.
- Refactored proc names, added "persistentObject" format for current
code.
- Renamed SQL table.
- Refactored logging and call hiearachy.
- Fix CI labeler adding DB label on non-SQL files.
This commit is contained in:
FabianK3
2026-03-24 19:53:37 +01:00
committed by GitHub
parent fd1c4ead05
commit aff736cda2
20 changed files with 425 additions and 353 deletions
+1 -1
View File
@@ -92,7 +92,7 @@
**/persistence @Arrow768 @NonQueueingMatt @FabianK3
/code/__DEFINES/persistence.dm @Arrow768 @NonQueueingMatt @FabianK3
/code/__HELPERS/logging/subsystems/persistence.dm @Arrow768 @NonQueueingMatt @FabianK3
/code/controllers/subsystems/persistence.dm @Arrow768 @NonQueueingMatt @FabianK3
/code/controllers/subsystems/persistence/ @Arrow768 @NonQueueingMatt @FabianK3
# Sprites, sammy
/icons/ @nauticall
+1 -1
View File
@@ -1,6 +1,6 @@
# Folder Rules
Database:
- SQL/**
- '**/*.sql'
'🗺️ Mapping - Away Ship/Away Site':
- maps/away/**
@@ -0,0 +1 @@
RENAME TABLE ss13_persistent_data TO ss13_persistent_objects;
+4 -1
View File
@@ -379,7 +379,6 @@
#include "code\controllers\subsystems\overlays.dm"
#include "code\controllers\subsystems\pai.dm"
#include "code\controllers\subsystems\pathfinder.dm"
#include "code\controllers\subsystems\persistence.dm"
#include "code\controllers\subsystems\ping.dm"
#include "code\controllers\subsystems\plants.dm"
#include "code\controllers\subsystems\profiler.dm"
@@ -431,6 +430,10 @@
#include "code\controllers\subsystems\movement\movement.dm"
#include "code\controllers\subsystems\movement\movement_types.dm"
#include "code\controllers\subsystems\movement\spacedrift.dm"
#include "code\controllers\subsystems\persistence\persistence.dm"
#include "code\controllers\subsystems\persistence\persistence_objects.dm"
#include "code\controllers\subsystems\persistence\persistence_objects_public.dm"
#include "code\controllers\subsystems\persistence\persistence_objects_sql.dm"
#include "code\controllers\subsystems\processing\airflow.dm"
#include "code\controllers\subsystems\processing\calamity.dm"
#include "code\controllers\subsystems\processing\disease.dm"
+3 -3
View File
@@ -111,9 +111,9 @@ GLOBAL_VAR(custom_event_msg)
GLOBAL_DATUM(dbcon, /DBConnection)
GLOBAL_PROTECT(dbcon)
// Persistence subsystem track register - List of all persistent data tracks managed by the subsystem.
GLOBAL_LIST_EMPTY(persistence_register)
GLOBAL_PROTECT(persistence_register)
// Persistence subsystem object track register - List of all persistent objects tracked by the subsystem.
GLOBAL_LIST_EMPTY(persistence_object_track_register)
GLOBAL_PROTECT(persistence_object_track_register)
// Added for Xenoarchaeology, might be useful for other stuff.
GLOBAL_LIST_INIT(alphabet_uppercase, list("A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"))
@@ -5,3 +5,15 @@
if (GLOB.config?.logsettings["log_subsystems_persistence"])
WRITE_LOG(GLOB.config.logfiles["world_subsystems_persistence_log"], "SSPersistence: [text]")
#endif
/proc/log_subsystem_persistence_info(text)
log_subsystem_persistence("INFO: [text]")
/proc/log_subsystem_persistence_warning(text)
log_subsystem_persistence("WARNING: [text]")
/proc/log_subsystem_persistence_error(text)
log_subsystem_persistence("ERROR: [text]")
/proc/log_subsystem_persistence_panic(text)
log_subsystem_persistence("PANIC: [text]")
-317
View File
@@ -1,317 +0,0 @@
SUBSYSTEM_DEF(persistence)
name = "Persistence"
init_order = INIT_ORDER_PERSISTENCE
flags = SS_NO_FIRE
/*#############################################
Internal methods
#############################################*/
/**
* Initialization of the persistence subsystem. Initialization includes loading all persistent data and spawning the related objects.
*/
/datum/controller/subsystem/persistence/Initialize()
. = ..()
try
if(!GLOB.config.sql_enabled)
log_subsystem_persistence("SQL configuration not enabled! Persistence subsystem requires SQL.")
return SS_INIT_SUCCESS
GLOB.persistence_register = list()
if(!SSdbcore.Connect())
log_subsystem_persistence("SQL ERROR during persistence subsystem init. Failed to connect.")
return SS_INIT_FAILURE
else
// Delete all persistent objects in the database that have expired and have passed the cleanup grace period (PERSISTENT_EXPIRATION_CLEANUP_DELAY_DAYS)
database_clean_entries()
// Retrieve all persistent data that is not expired
var/list/persistent_data = database_get_active_entries()
log_subsystem_persistence("Init: Retrieved [persistent_data.len] 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.persistence_track_id = data["id"]
track_apply_content(instance, data["content"], data["x"], data["y"], data["z"])
register_track(instance, data["author_ckey"])
return SS_INIT_SUCCESS
catch(var/exception/e)
log_subsystem_persistence("Panic: Exception during subsystem initialize: [e]")
return SS_INIT_FAILURE
/**
* Shutdown of the persistence subsystem. Adds new persistent objects, removes no longer existing persistent objects and updates changed persistent objects in the database.
*/
/datum/controller/subsystem/persistence/Shutdown()
try
if(!GLOB.config.sql_enabled)
log_subsystem_persistence("SQL configuration not enabled! Panic - Cannot save current round track changes!")
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 GLOB.persistence_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
deregister_track(track)
if(astype(track, /obj/item)?.in_inventory) // Objects that are held by players won't become persistent
deregister_track(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 = database_get_active_entries()
for (var/obj/track in GLOB.persistence_register)
CHECK_TICK
if (track.persistence_track_id == 0)
// Tracked object has no ID meaning it is new, create a new persistent record for it
database_add_entry(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 GLOB.persistence_register)
CHECK_TICK
if (record["id"] == track.persistence_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 (track_get_content(track) != record["content"])
changed = TRUE
if (changed)
database_update_entry(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)
database_expire_entry(record["id"])
expired++
log_subsystem_persistence("Shutdown: Tried to create [created], update [updated] and expire [expired] tracks.")
catch(var/exception/e)
log_subsystem_persistence("Panic: Exception during subsystem shutdown: [e]")
return
/**
* Generates StatEntry. Returns information about currently tracked objects.
*/
/datum/controller/subsystem/persistence/stat_entry(msg)
msg = ("Global register tracks: [GLOB.persistence_register.len]")
return ..()
/**
* Safely get JSON persistent content of track.
* RETURN: JSON formatted content of track or null if an exception occured.
*/
/datum/controller/subsystem/persistence/proc/track_get_content(var/obj/track)
var/result = json_encode(list())
try
var/list/content = track.persistence_get_content()
if(content && content.len)
result = json_encode(content)
catch(var/exception/e)
log_subsystem_persistence("Track: Failed to get/encode track content: [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/track_apply_content(var/obj/track, var/json, var/x, var/y, var/z)
try
track.persistence_apply_content(json_decode(json), x, y, z)
catch(var/exception/e)
log_subsystem_persistence("Track: Failed to apply/decode track content: [e]")
/**
* Run cleanup on the persistence entries in the database.
* Cleanup includes all entries that have expired and have passed the clean up grace period (PERSISTENT_EXPIRATION_CLEANUP_DELAY_DAYS).
*/
/datum/controller/subsystem/persistence/proc/database_clean_entries()
if(!SSdbcore.Connect())
log_subsystem_persistence("SQL ERROR during persistence database_clean_entries. Failed to connect.")
else
var/datum/db_query/cleanup_query = SSdbcore.NewQuery(
"DELETE FROM ss13_persistent_data WHERE DATE_ADD(expires_at, INTERVAL :grace_period_days DAY) <= NOW()",
list("grace_period_days" = PERSISTENT_EXPIRATION_CLEANUP_DELAY_DAYS)
)
cleanup_query.SetFailCallback(CALLBACK(PROC_REF(database_clean_entries_callback_failure)))
cleanup_query.SetSuccessCallback(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel)))
cleanup_query.ExecuteNoSleep(TRUE)
/datum/controller/subsystem/persistence/proc/database_clean_entries_callback_failure(var/datum/db_query/cleanup_query)
if (cleanup_query.ErrorMsg())
log_subsystem_persistence("SQL ERROR during persistence database_clean_entries. " + cleanup_query.ErrorMsg())
qdel(cleanup_query)
/**
* Retrieve persistent data entries that haven't expired.
* RETURN: List of JSON, with ID, author_ckey, type, content, x, y, z
*/
/datum/controller/subsystem/persistence/proc/database_get_active_entries()
if(!SSdbcore.Connect())
log_subsystem_persistence("SQL ERROR during persistence database_get_active_entries. Failed to connect.")
else
var/datum/db_query/get_query = SSdbcore.NewQuery(
"SELECT id, author_ckey, type, content, x, y, z FROM ss13_persistent_data WHERE NOW() < expires_at"
)
get_query.Execute()
var/list/results = list()
if (get_query.ErrorMsg())
log_subsystem_persistence("SQL ERROR during persistence database_get_active_entries. " + get_query.ErrorMsg())
return
else
while (get_query.NextRow())
CHECK_TICK
var/list/entry = list(
"id" = get_query.item[1],
"author_ckey" = get_query.item[2],
"type" = get_query.item[3],
"content" = get_query.item[4],
"x" = get_query.item[5],
"y" = get_query.item[6],
"z" = get_query.item[7]
)
results += list(entry)
qdel(get_query)
return results
/**
* Adds a persistent data record to the database.
*/
/datum/controller/subsystem/persistence/proc/database_add_entry(var/obj/track)
if(!SSdbcore.Connect())
log_subsystem_persistence("SQL ERROR during persistence database_add_entry. Failed to connect.")
else
var/turf/T = get_turf(track)
if(!T)
return
var/datum/db_query/insert_query = SSdbcore.NewQuery(
"INSERT INTO ss13_persistent_data (author_ckey, type, created_at, expires_at, content, x, y, z) \
VALUES (:author_ckey, :type, NOW(), DATE_ADD(NOW(), INTERVAL :expire_in_days DAY), :content, :x, :y, :z)",
list(
"author_ckey" = track.persistence_author_ckey,
"type" = "[track.type]",
"expire_in_days" = track.persistance_expiration_time_days,
"content" = track_get_content(track),
"x" = T.x,
"y" = T.y,
"z" = T.z
)
)
insert_query.Execute()
if (insert_query.ErrorMsg())
log_subsystem_persistence("SQL ERROR during persistence database_add_entry. " + insert_query.ErrorMsg())
qdel(insert_query)
/**
* Updates a persistent data record in the database.
*/
/datum/controller/subsystem/persistence/proc/database_update_entry(var/obj/track)
if(!SSdbcore.Connect())
log_subsystem_persistence("SQL ERROR during persistence database_update_entry. Failed to connect.")
else
var/turf/T = get_turf(track)
if(!T)
return
var/datum/db_query/update_query = SSdbcore.NewQuery(
"UPDATE ss13_persistent_data SET author_ckey=:author_ckey, expires_at=DATE_ADD(NOW(), INTERVAL :expire_in_days DAY), content=:content, x=:x, y=:y, z=:z WHERE id = :id",
list(
"author_ckey" = track.persistence_author_ckey,
"expire_in_days" = track.persistance_expiration_time_days,
"content" = track_get_content(track),
"x" = T.x,
"y" = T.y,
"z" = T.z,
"id" = track.persistence_track_id
)
)
update_query.Execute()
if (update_query.ErrorMsg())
log_subsystem_persistence("SQL ERROR during persistence database_update_entry. " + update_query.ErrorMsg())
qdel(update_query)
/**
* Expire a persistent data record in the database by setting it's expiration date to now.
*/
/datum/controller/subsystem/persistence/proc/database_expire_entry(var/track_id)
if(!SSdbcore.Connect())
log_subsystem_persistence("SQL ERROR during persistence database_expire_entry. Failed to connect.")
else
var/datum/db_query/expire_query = SSdbcore.NewQuery(
"UPDATE ss13_persistent_data SET expires_at=NOW() WHERE id = :id",
list("id" = track_id)
)
expire_query.Execute()
if (expire_query.ErrorMsg())
log_subsystem_persistence("SQL ERROR during persistence database_expire_entry. " + expire_query.ErrorMsg())
qdel(expire_query)
/*#############################################
Public methods
#############################################*/
/**
* Adds the given object to the list of tracked objects. At shutdown the tracked object will be either created or updated in the database.
* The ckey is an optional argument and is used for tracking user generated content by adding an author to the persistent data.
*/
/datum/controller/subsystem/persistence/proc/register_track(var/obj/new_track, var/ckey)
if(new_track.persistence_track_active) // Prevent multiple registers per object and removes the need to check the register if it's already in there
return
var/turf/T = get_turf(new_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
return
new_track.persistence_track_active = TRUE
new_track.persistence_author_ckey = ckey
GLOB.persistence_register += new_track
/**
* Removes the given object from the list of tracked objects. At shutdown the tracked object will be remove from the database.
*/
/datum/controller/subsystem/persistence/proc/deregister_track(var/obj/old_track)
if(!old_track.persistence_track_active) // Prevent multiple deregisters per object and removes the need to check the register if it's not in there
return
old_track.persistence_track_active = FALSE
GLOB.persistence_register -= old_track
@@ -0,0 +1,85 @@
/*
* Persistence subsystem
* Subsytem for managing any form of persistent content across rounds.
*
* This subsystem consists of multiple partial files, following the structure:
* - persistence.dm - Subsystem definition and generic code.
* - persistence_objects.dm - Persistent objects related code.
* - persistence_objects_sql.dm - Persistent objects database code.
* - persistence_objects_public.dm - Persistent objects public procs.
*/
SUBSYSTEM_DEF(persistence)
name = "Persistence"
init_order = INIT_ORDER_PERSISTENCE // The order is tied with the init and maploading subsystem.
flags = SS_NO_FIRE // This subsystem has no continues workload, it's init and shutdown only.
/**
* Subsystem info stub message generation.
*/
/datum/controller/subsystem/persistence/stat_entry(msg)
msg = ("Tracked object register: [length(GLOB.persistence_object_track_register)]")
return ..()
/**
* Helper method to check and log database connection.
* RETURN: True if connection is scuccessful, false if not.
* PARAMS:
* action = Custom string of the action being performed written to log.
*/
/datum/controller/subsystem/persistence/proc/databaseCheckConnection(action = "unlabeled action")
PRIVATE_PROC(TRUE)
if(!SSdbcore.Connect())
log_subsystem_persistence_error("SQL error during [action], connection failed.")
return FALSE
return TRUE
/**
* Helper method to check the SQL query result and log possible errors.
* RETURN: True if no error occured, false if an error was found.
*/
/datum/controller/subsystem/persistence/proc/databaseCheckQueryResult(datum/db_query/query, action = "unlabeled action")
PRIVATE_PROC(TRUE)
if (!query)
log_subsystem_persistence_error("SQL error during [action], in addition query object provided to check was null.")
return FALSE
if (query.ErrorMsg())
log_subsystem_persistence_error("SQL error during [action]. " + query.ErrorMsg())
return FALSE
return TRUE
/**
* Initialization of the persistence subsystem.
* Includes generic startup checks and init of the different persistent data types.
*/
/datum/controller/subsystem/persistence/Initialize()
. = ..()
if(!GLOB.config.sql_enabled)
log_subsystem_persistence_warning("SQL configuration not enabled. Persistence subsystem requires SQL. Skipping init.")
return SS_INIT_SUCCESS
if(!databaseCheckConnection("subsystem init"))
return SS_INIT_FAILURE
try
objectsInitialize()
catch(var/exception/e)
log_subsystem_persistence_panic("Unhandled exception during persistent objects initialization: [e]")
return SS_INIT_FAILURE
return SS_INIT_SUCCESS
/**
* Shutdown of the persistence subsystem.
* The shutdown consists of finalization steps for each persistent data type.
*/
/datum/controller/subsystem/persistence/Shutdown()
if(!databaseCheckConnection("subsystem shutdown"))
log_subsystem_persistence_panic("SQL error during persistence subsystem shutdown. Cannot finalise persistence of the round.")
return
try
objectsFinalize()
catch(var/exception/e)
log_subsystem_persistence_panic("Unhandled exception during persistent objects finalization: [e]")
return
@@ -0,0 +1,124 @@
/**
* 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)
GLOB.persistence_object_track_register = list()
// 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"]
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)
// 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 as anything in GLOB.persistence_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)
if(astype(track, /obj/item)?.in_inventory) // Objects that are held by players won't become persistent
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 as anything in GLOB.persistence_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 as anything in GLOB.persistence_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 for persistent object. Failed to get/encode track content: [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 for persistent object. Failed to apply/decode track content: [e]")
@@ -0,0 +1,28 @@
/* This partial file contains all procs that are meant for public access. */
/* Other procs should be made private. */
/**
* Adds the given object to the list of tracked objects. At shutdown the tracked object will be either created or updated in the database.
* The ckey is an optional argument and is used for tracking user generated content by adding an author to the persistent data.
*/
/datum/controller/subsystem/persistence/proc/objectsRegisterTrack(obj/new_track, ckey)
if(new_track.persistent_objects_track_active) // Prevent multiple registers per object and removes the need to check the register if it's already in there
return
var/turf/T = get_turf(new_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
return
new_track.persistent_objects_track_active = TRUE
new_track.persistent_objects_author_ckey = ckey
GLOB.persistence_object_track_register += new_track
/**
* Removes the given object from the list of tracked objects. At shutdown the tracked object will be remove from the database.
*/
/datum/controller/subsystem/persistence/proc/objectsDeregisterTrack(obj/old_track)
if(!old_track.persistent_objects_track_active) // Prevent multiple deregisters per object and removes the need to check the register if it's not in there
return
old_track.persistent_objects_track_active = FALSE
GLOB.persistence_object_track_register -= old_track
@@ -0,0 +1,130 @@
/**
* Run cleanup on the persistence entries in the database.
* Cleanup includes all entries that have expired and have passed the clean up grace period (PERSISTENT_EXPIRATION_CLEANUP_DELAY_DAYS).
*/
/datum/controller/subsystem/persistence/proc/objectsDatabaseCleanEntries()
PRIVATE_PROC(TRUE)
if(!databaseCheckConnection("objectsDatabaseCleanEntries"))
return
var/datum/db_query/cleanup_query = SSdbcore.NewQuery(
"DELETE FROM ss13_persistent_objects WHERE DATE_ADD(expires_at, INTERVAL :grace_period_days DAY) <= NOW()",
list("grace_period_days" = PERSISTENT_EXPIRATION_CLEANUP_DELAY_DAYS)
)
cleanup_query.SetFailCallback(CALLBACK(PROC_REF(objectsDatabaseCleanEntries_CallbackFailure)))
cleanup_query.SetSuccessCallback(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel)))
cleanup_query.ExecuteNoSleep(TRUE)
/datum/controller/subsystem/persistence/proc/objectsDatabaseCleanEntries_CallbackFailure(datum/db_query/cleanup_query)
databaseCheckQueryResult(cleanup_query, "objectsDatabaseCleanEntries")
qdel(cleanup_query)
/**
* Retrieve persistent data entries that haven't expired.
* RETURN: List of JSON, with ID, author_ckey, type, content, x, y, z
*/
/datum/controller/subsystem/persistence/proc/objectsDatabaseGetActiveEntries()
PRIVATE_PROC(TRUE)
if(!databaseCheckConnection("objectsDatabaseGetActiveEntries"))
return
var/datum/db_query/get_query = SSdbcore.NewQuery(
"SELECT id, author_ckey, type, content, x, y, z FROM ss13_persistent_objects WHERE NOW() < expires_at"
)
get_query.Execute()
var/list/results = list()
if (!databaseCheckQueryResult(get_query, "objectsDatabaseGetActiveEntries"))
return
else
while (get_query.NextRow())
CHECK_TICK
var/list/entry = list(
"id" = get_query.item[1],
"author_ckey" = get_query.item[2],
"type" = get_query.item[3],
"content" = get_query.item[4],
"x" = get_query.item[5],
"y" = get_query.item[6],
"z" = get_query.item[7]
)
results += list(entry)
qdel(get_query)
return results
/**
* Adds a persistent data record to the database.
*/
/datum/controller/subsystem/persistence/proc/objectsDatabaseAddEntry(obj/track)
PRIVATE_PROC(TRUE)
if(!databaseCheckConnection("objectsDatabaseAddEntry"))
return
var/turf/T = get_turf(track)
if(!T)
return
var/datum/db_query/insert_query = SSdbcore.NewQuery(
"INSERT INTO ss13_persistent_objects (author_ckey, type, created_at, expires_at, content, x, y, z) \
VALUES (:author_ckey, :type, NOW(), DATE_ADD(NOW(), INTERVAL :expire_in_days DAY), :content, :x, :y, :z)",
list(
"author_ckey" = track.persistent_objects_author_ckey,
"type" = "[track.type]",
"expire_in_days" = track.persistant_objects_expiration_time_days,
"content" = objectsGetTrackContent(track),
"x" = T.x,
"y" = T.y,
"z" = T.z
)
)
insert_query.Execute()
databaseCheckQueryResult(insert_query, "objectsDatabaseAddEntry")
qdel(insert_query)
/**
* Updates a persistent data record in the database.
*/
/datum/controller/subsystem/persistence/proc/objectsDatabaseUpdateEntry(obj/track)
PRIVATE_PROC(TRUE)
if(!databaseCheckConnection("objectsDatabaseUpdateEntry"))
return
var/turf/T = get_turf(track)
if(!T)
return
var/datum/db_query/update_query = SSdbcore.NewQuery(
"UPDATE ss13_persistent_objects SET author_ckey=:author_ckey, expires_at=DATE_ADD(NOW(), INTERVAL :expire_in_days DAY), content=:content, x=:x, y=:y, z=:z WHERE id = :id",
list(
"author_ckey" = track.persistent_objects_author_ckey,
"expire_in_days" = track.persistant_objects_expiration_time_days,
"content" = objectsGetTrackContent(track),
"x" = T.x,
"y" = T.y,
"z" = T.z,
"id" = track.persistent_objects_track_id
)
)
update_query.Execute()
databaseCheckQueryResult(update_query, "objectsDatabaseUpdateEntry")
qdel(update_query)
/**
* Expire a persistent data record in the database by setting it's expiration date to now.
*/
/datum/controller/subsystem/persistence/proc/objectsDatabaseExpireEntry(track_id)
PRIVATE_PROC(TRUE)
if(!databaseCheckConnection("objectsDatabaseExpireEntry"))
return
var/datum/db_query/expire_query = SSdbcore.NewQuery(
"UPDATE ss13_persistent_objects SET expires_at=NOW() WHERE id = :id",
list("id" = track_id)
)
expire_query.Execute()
databaseCheckQueryResult(expire_query, "objectsDatabaseExpireEntry")
qdel(expire_query)
+4 -4
View File
@@ -507,7 +507,7 @@
return
if(in_storage) // Items getting moved into storages (lunchboxes, backpacks) triggers the dropped handler and requires no persistency as a result
SSpersistence.deregister_track(src)
SSpersistence.objectsDeregisterTrack(src)
return
// Trash-like items should become only persistent when they are not dropped in an area flagged with AREA_FLAG_PREVENT_PERSISTENT_TRASH
@@ -515,12 +515,12 @@
if(T)
var/area/A = get_area(T)
if(A && !(A.area_flags & AREA_FLAG_PREVENT_PERSISTENT_TRASH))
persistance_expiration_time_days = 3 // Ensure expiration date is set to prevent long term trash
SSpersistence.register_track(src, usr == null ? null : ckey(usr.key))
persistant_objects_expiration_time_days = 3 // Ensure expiration date is set to prevent long term trash
SSpersistence.objectsRegisterTrack(src, usr == null ? null : ckey(usr.key))
return
// Fallback - No persistency
SSpersistence.deregister_track(src)
SSpersistence.objectsDeregisterTrack(src)
/obj/item/proc/remove_item_verbs(mob/user)
if(ismech(user)) //very snowflake, but necessary due to how mechs work
+2 -2
View File
@@ -15,7 +15,7 @@
/obj/item/trash/attack(mob/living/target_mob, mob/living/user, target_zone)
return
/obj/item/trash/persistence_get_content()
/obj/item/trash/persistent_objects_get_content()
var/list/content = list()
content["name"] = name
content["desc"] = desc
@@ -26,7 +26,7 @@
content["pickup_sound"] = pickup_sound
return content
/obj/item/trash/persistence_apply_content(content, x, y, z)
/obj/item/trash/persistent_objects_apply_content(content, x, y, z)
name = content["name"]
desc = content["desc"]
icon = file(content["icon"])
@@ -11,19 +11,19 @@
/obj/item/material/ashtray/Initialize(newloc, material_key)
. = ..()
persistance_expiration_time_days = rand(7, 180) // Imagine they get stolen, lost or break...
persistant_objects_expiration_time_days = rand(7, 180) // Imagine they get stolen, lost or break...
max_butts = round(material.hardness/10) //This is arbitrary but whatever.
randpixel_xy()
update_icon()
SSpersistence.register_track(src, null)
SSpersistence.objectsRegisterTrack(src, null)
/obj/item/material/ashtray/persistence_get_content()
/obj/item/material/ashtray/persistent_objects_get_content()
var/list/content = list()
content["fill_count"] = length(contents)
content["material"] = material.name
return content
/obj/item/material/ashtray/persistence_apply_content(content, x, y, z)
/obj/item/material/ashtray/persistent_objects_apply_content(content, x, y, z)
src.x = x
src.y = y
src.z = z
@@ -89,7 +89,7 @@
attacking_item.forceMove(src)
if(istype(attacking_item, /obj/item/trash/cigbutt))
SSpersistence.deregister_track(attacking_item) // Ashtray will handle the persistent contents in it itself
SSpersistence.objectsDeregisterTrack(attacking_item) // Ashtray will handle the persistent contents in it itself
if (istype(attacking_item,/obj/item/clothing/mask/smokable/cigarette))
var/obj/item/clothing/mask/smokable/cigarette/cig = attacking_item
+8 -8
View File
@@ -81,21 +81,21 @@
/* START PERSISTENCE VARS */
// State check if the subsystem is tracking the object, used for easy state checking without iterating the register
var/persistence_track_active = FALSE
var/persistent_objects_track_active = FALSE
// Tracking ID of the object used by the persistence subsystem
var/persistence_track_id = 0
var/persistent_objects_track_id = 0
// Author ckey of the object used in persistence subsystem
// Note: Not every type can have an author, like generated dirt for example
// Additionally, the ckey is only an indicator, for example: A player could pin a paper without having written it
// This should be considered for any moderation purpose
var/persistence_author_ckey = null
var/persistent_objects_author_ckey = null
// Expiration time used when saving/updating a persistent type, this can be changed depending on the use case by assigning a new value
var/persistance_expiration_time_days = PERSISTENT_DEFAULT_EXPIRATION_DAYS
var/persistant_objects_expiration_time_days = PERSISTENT_DEFAULT_EXPIRATION_DAYS
/* END PERSISTENCE VARS */
/obj/Destroy()
if(persistence_track_active) // Prevent hard deletion of references in the persistence register by removing it preemptively
SSpersistence.deregister_track(src)
if(persistent_objects_track_active) // Prevent hard deletion of references in the persistence register by removing it preemptively
SSpersistence.objectsDeregisterTrack(src)
STOP_PROCESSING(SSprocessing, src)
unbuckle()
QDEL_NULL(talking_atom)
@@ -388,7 +388,7 @@
* Expected to be overriden by derived objects.
* RETURN: Associated list with custom information (e.g.: ["test" = "abc", "counter" = 123])
*/
/obj/proc/persistence_get_content()
/obj/proc/persistent_objects_get_content()
return
/**
@@ -398,5 +398,5 @@
* content = Associated list with custom information (e.g.: ["test" = "abc", "counter" = 123]).
* x,y,z = x-y-z coordinates of object, can be null.
*/
/obj/proc/persistence_apply_content(content, x, y, z)
/obj/proc/persistent_objects_apply_content(content, x, y, z)
return
+2 -2
View File
@@ -293,7 +293,7 @@
icon_state = "folded_wall-torn"
persistency_considered_trash = TRUE
/obj/item/inflatable/torn/persistence_apply_content(content, x, y, z)
/obj/item/inflatable/torn/persistent_objects_apply_content(content, x, y, z)
src.x = x
src.y = y
src.z = z
@@ -308,7 +308,7 @@
icon_state = "folded_door-torn"
persistency_considered_trash = TRUE
/obj/item/inflatable/door/torn/persistence_apply_content(content, x, y, z)
/obj/item/inflatable/door/torn/persistent_objects_apply_content(content, x, y, z)
src.x = x
src.y = y
src.z = z
+2 -2
View File
@@ -36,7 +36,7 @@
user.drop_from_inventory(attacking_item, src)
notices++
update_icon()
SSpersistence.register_track(attacking_item, ckey(usr.key)) // Add paper to persistent tracker
SSpersistence.objectsRegisterTrack(attacking_item, ckey(usr.key)) // Add paper to persistent tracker
to_chat(user, SPAN_NOTICE("You pin the paper to the noticeboard."))
else
to_chat(user, SPAN_NOTICE("\The [src] is already full of papers and can not fit another."))
@@ -71,7 +71,7 @@
add_fingerprint(usr)
notices--
update_icon()
SSpersistence.deregister_track(P) // Remove paper from persistent tracker
SSpersistence.objectsDeregisterTrack(P) // Remove paper from persistent tracker
if(href_list["write"])
if((usr.stat || usr.restrained())) //For when a player is handcuffed while they have the notice window open
return
+6 -6
View File
@@ -822,13 +822,13 @@
PERSISTENT
#############################################*/
/obj/item/paper/persistence_get_content()
/obj/item/paper/persistent_objects_get_content()
var/list/content = list()
content["title"] = name
content["text"] = info
return content
/obj/item/paper/persistence_apply_content(content, x, y, z)
/obj/item/paper/persistent_objects_apply_content(content, x, y, z)
set_content(content["title"], content["text"])
src.x = x
src.y = y
@@ -865,14 +865,14 @@
icon_state = info ? "stickynote_words" : "stickynote"
/obj/item/paper/stickynotes/persistence_get_content()
/obj/item/paper/stickynotes/persistent_objects_get_content()
var/list/content = ..()
content["color"] = color
content["pixel_x"] = pixel_x
content["pixel_y"] = pixel_y
return content
/obj/item/paper/stickynotes/persistence_apply_content(content, x, y, z)
/obj/item/paper/stickynotes/persistent_objects_apply_content(content, x, y, z)
src.name = content["title"]
src.info = content["text"]
src.color = content["color"]
@@ -883,7 +883,7 @@
src.z = z
/obj/item/paper/stickynotes/pickup()
SSpersistence.deregister_track(src)
SSpersistence.objectsDeregisterTrack(src)
..()
/obj/item/paper/stickynotes/afterattack(var/A, mob/user, var/prox, var/params)
@@ -900,7 +900,7 @@
if(!params || !prox)
return
SSpersistence.register_track(src, ckey(user.key))
SSpersistence.objectsRegisterTrack(src, ckey(user.key))
user.drop_from_inventory(src,source_turf)
if(params) //Parallels taped paper placement method, and avoids seeing stickynotes through walls
var/list/mouse_control = mouse_safe_xy(params)
@@ -249,7 +249,7 @@
w_class = WEIGHT_CLASS_SMALL
persistency_considered_trash = TRUE
/obj/item/broken_bottle/persistence_apply_content(content, x, y, z)
/obj/item/broken_bottle/persistent_objects_apply_content(content, x, y, z)
src.x = x
src.y = y
src.z = z
@@ -0,0 +1,6 @@
author: FabianK3
delete-after: True
changes:
- code_imp: "Overhauled the current persistence subsystem for upcoming additions. No changes to current mechanics."