mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-08-21 20:11:04 +01:00
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:
@@ -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)
|
||||
Reference in New Issue
Block a user