From 92128b98fd5e77f3def5f9c0977da87021bfb630 Mon Sep 17 00:00:00 2001 From: Chris Miles Date: Sun, 30 Mar 2025 14:46:02 -0500 Subject: [PATCH] [Instances] Add `expire_at` Column (#4820) * [Instances] Add `expire_at` Column * expire_at update * Update servertalk.h * Add rule Instances:ExpireOffsetTimeSeconds --- common/database/database_update_manifest.cpp | 14 +++++++ common/database_instances.cpp | 34 +++++++-------- common/dynamic_zone_base.cpp | 1 + .../base/base_instance_list_repository.h | 28 +++++++++---- .../repositories/instance_list_repository.h | 42 +------------------ common/ruletypes.h | 1 + common/version.h | 2 +- world/dynamic_zone.cpp | 7 +++- zone/questmgr.cpp | 1 + 9 files changed, 62 insertions(+), 68 deletions(-) diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index 54e5b549b..9127db89b 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -7068,6 +7068,20 @@ ADD COLUMN `expire_at` int(11) UNSIGNED NULL DEFAULT 0 AFTER `duration`; UPDATE respawn_times set expire_at = `start` + `duration`; -- backfill existing data CREATE INDEX `idx_expire_at` ON `respawn_times` (`expire_at`); +)", + .content_schema_update = false + }, + ManifestEntry{ + .version = 9321, + .description = "2025_03_30_instance_list_add_expire_at.sql", + .check = "SHOW COLUMNS FROM `instance_list` LIKE 'expire_at'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `instance_list` +ADD COLUMN `expire_at` bigint(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `duration`; + +CREATE INDEX `idx_expire_at` ON `instance_list` (`expire_at`); )", .content_schema_update = false }, diff --git a/common/database_instances.cpp b/common/database_instances.cpp index 00c9a2476..c1d521cb0 100644 --- a/common/database_instances.cpp +++ b/common/database_instances.cpp @@ -128,6 +128,7 @@ bool Database::CreateInstance(uint16 instance_id, uint32 zone_id, uint32 version e.version = version; e.start_time = std::time(nullptr); e.duration = duration; + e.expire_at = e.start_time + duration; RespawnTimesRepository::ClearInstanceTimers(*this, e.id); InstanceListRepository::ReplaceOne(*this, e); @@ -560,14 +561,12 @@ void Database::GetCharactersInInstance(uint16 instance_id, std::list &ch void Database::PurgeExpiredInstances() { - /** - * Delay purging by a day so that we can continue using adjacent free instance id's - * from the table without risking the chance we immediately re-allocate a zone that freshly expired but - * has not been fully de-allocated - */ auto l = InstanceListRepository::GetWhere( *this, - "(start_time + duration) <= (UNIX_TIMESTAMP() - 86400) AND never_expires = 0" + fmt::format( + "expire_at <= (UNIX_TIMESTAMP() - {}) AND never_expires = 0", + RuleI(Instances, ExpireOffsetTimeSeconds) + ) ); if (l.empty()) { return; @@ -578,19 +577,19 @@ void Database::PurgeExpiredInstances() instance_ids.emplace_back(std::to_string(e.id)); } - const auto imploded_instance_ids = Strings::Implode(",", instance_ids); + const auto ids = Strings::Implode(",", instance_ids); - InstanceListRepository::DeleteWhere(*this, fmt::format("id IN ({})", imploded_instance_ids)); - InstanceListPlayerRepository::DeleteWhere(*this, fmt::format("id IN ({})", imploded_instance_ids)); - RespawnTimesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids)); - SpawnConditionValuesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids)); - CharacterCorpsesRepository::BuryInstances(*this, imploded_instance_ids); - DynamicZoneMembersRepository::DeleteByManyInstances(*this, imploded_instance_ids); - DynamicZonesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids)); - Spawn2DisabledRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids)); - DataBucketsRepository::DeleteWhere(*this, fmt::format("instance_id != 0 and instance_id IN ({})", imploded_instance_ids)); + InstanceListRepository::DeleteWhere(*this, fmt::format("id IN ({})", ids)); + InstanceListPlayerRepository::DeleteWhere(*this, fmt::format("id IN ({})", ids)); + RespawnTimesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids)); + SpawnConditionValuesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids)); + CharacterCorpsesRepository::BuryInstances(*this, ids); + DynamicZoneMembersRepository::DeleteByManyInstances(*this, ids); + DynamicZonesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids)); + Spawn2DisabledRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids)); + DataBucketsRepository::DeleteWhere(*this, fmt::format("instance_id != 0 and instance_id IN ({})", ids)); if (RuleB(Zone, StateSavingOnShutdown)) { - ZoneStateSpawnsRepository::DeleteWhere(*this, fmt::format("`instance_id` IN ({})", imploded_instance_ids)); + ZoneStateSpawnsRepository::DeleteWhere(*this, fmt::format("`instance_id` IN ({})", ids)); } } @@ -603,6 +602,7 @@ void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration) i.start_time = std::time(nullptr); i.duration = new_duration; + i.expire_at = i.start_time + i.duration; InstanceListRepository::UpdateOne(*this, i); } diff --git a/common/dynamic_zone_base.cpp b/common/dynamic_zone_base.cpp index e4a146b41..18fe69aa8 100644 --- a/common/dynamic_zone_base.cpp +++ b/common/dynamic_zone_base.cpp @@ -58,6 +58,7 @@ uint32_t DynamicZoneBase::CreateInstance() insert_instance.start_time = static_cast(std::chrono::system_clock::to_time_t(m_start_time)); insert_instance.duration = static_cast(m_duration.count()); insert_instance.never_expires = m_never_expires; + insert_instance.expire_at = insert_instance.start_time + insert_instance.duration; auto instance = InstanceListRepository::InsertOne(GetDatabase(), insert_instance); if (instance.id == 0) diff --git a/common/repositories/base/base_instance_list_repository.h b/common/repositories/base/base_instance_list_repository.h index 4b0d730a4..80e64d453 100644 --- a/common/repositories/base/base_instance_list_repository.h +++ b/common/repositories/base/base_instance_list_repository.h @@ -25,6 +25,7 @@ public: uint8_t is_global; uint32_t start_time; uint32_t duration; + uint64_t expire_at; uint8_t never_expires; std::string notes; }; @@ -43,6 +44,7 @@ public: "is_global", "start_time", "duration", + "expire_at", "never_expires", "notes", }; @@ -57,6 +59,7 @@ public: "is_global", "start_time", "duration", + "expire_at", "never_expires", "notes", }; @@ -105,6 +108,7 @@ public: e.is_global = 0; e.start_time = 0; e.duration = 0; + e.expire_at = 0; e.never_expires = 0; e.notes = ""; @@ -149,8 +153,9 @@ public: e.is_global = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; e.start_time = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; e.duration = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.never_expires = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.notes = row[7] ? row[7] : ""; + e.expire_at = row[6] ? strtoull(row[6], nullptr, 10) : 0; + e.never_expires = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.notes = row[8] ? row[8] : ""; return e; } @@ -189,8 +194,9 @@ public: v.push_back(columns[3] + " = " + std::to_string(e.is_global)); v.push_back(columns[4] + " = " + std::to_string(e.start_time)); v.push_back(columns[5] + " = " + std::to_string(e.duration)); - v.push_back(columns[6] + " = " + std::to_string(e.never_expires)); - v.push_back(columns[7] + " = '" + Strings::Escape(e.notes) + "'"); + v.push_back(columns[6] + " = " + std::to_string(e.expire_at)); + v.push_back(columns[7] + " = " + std::to_string(e.never_expires)); + v.push_back(columns[8] + " = '" + Strings::Escape(e.notes) + "'"); auto results = db.QueryDatabase( fmt::format( @@ -218,6 +224,7 @@ public: v.push_back(std::to_string(e.is_global)); v.push_back(std::to_string(e.start_time)); v.push_back(std::to_string(e.duration)); + v.push_back(std::to_string(e.expire_at)); v.push_back(std::to_string(e.never_expires)); v.push_back("'" + Strings::Escape(e.notes) + "'"); @@ -255,6 +262,7 @@ public: v.push_back(std::to_string(e.is_global)); v.push_back(std::to_string(e.start_time)); v.push_back(std::to_string(e.duration)); + v.push_back(std::to_string(e.expire_at)); v.push_back(std::to_string(e.never_expires)); v.push_back("'" + Strings::Escape(e.notes) + "'"); @@ -296,8 +304,9 @@ public: e.is_global = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; e.start_time = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; e.duration = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.never_expires = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.notes = row[7] ? row[7] : ""; + e.expire_at = row[6] ? strtoull(row[6], nullptr, 10) : 0; + e.never_expires = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.notes = row[8] ? row[8] : ""; all_entries.push_back(e); } @@ -328,8 +337,9 @@ public: e.is_global = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; e.start_time = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; e.duration = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.never_expires = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.notes = row[7] ? row[7] : ""; + e.expire_at = row[6] ? strtoull(row[6], nullptr, 10) : 0; + e.never_expires = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.notes = row[8] ? row[8] : ""; all_entries.push_back(e); } @@ -410,6 +420,7 @@ public: v.push_back(std::to_string(e.is_global)); v.push_back(std::to_string(e.start_time)); v.push_back(std::to_string(e.duration)); + v.push_back(std::to_string(e.expire_at)); v.push_back(std::to_string(e.never_expires)); v.push_back("'" + Strings::Escape(e.notes) + "'"); @@ -440,6 +451,7 @@ public: v.push_back(std::to_string(e.is_global)); v.push_back(std::to_string(e.start_time)); v.push_back(std::to_string(e.duration)); + v.push_back(std::to_string(e.expire_at)); v.push_back(std::to_string(e.never_expires)); v.push_back("'" + Strings::Escape(e.notes) + "'"); diff --git a/common/repositories/instance_list_repository.h b/common/repositories/instance_list_repository.h index e177e5063..dbaf8d2a4 100644 --- a/common/repositories/instance_list_repository.h +++ b/common/repositories/instance_list_repository.h @@ -7,49 +7,11 @@ class InstanceListRepository: public BaseInstanceListRepository { public: - - /** - * This file was auto generated and can be modified and extended upon - * - * Base repository methods are automatically - * generated in the "base" version of this repository. The base repository - * is immutable and to be left untouched, while methods in this class - * are used as extension methods for more specific persistence-layer - * accessors or mutators. - * - * Base Methods (Subject to be expanded upon in time) - * - * Note: Not all tables are designed appropriately to fit functionality with all base methods - * - * InsertOne - * UpdateOne - * DeleteOne - * FindOne - * GetWhere(std::string where_filter) - * DeleteWhere(std::string where_filter) - * InsertMany - * All - * - * Example custom methods in a repository - * - * InstanceListRepository::GetByZoneAndVersion(int zone_id, int zone_version) - * InstanceListRepository::GetWhereNeverExpires() - * InstanceListRepository::GetWhereXAndY() - * InstanceListRepository::DeleteWhereXAndY() - * - * Most of the above could be covered by base methods, but if you as a developer - * find yourself re-using logic for other parts of the code, its best to just make a - * method that can be re-used easily elsewhere especially if it can use a base repository - * method and encapsulate filters there - */ - - // Custom extended repository methods here - static int UpdateDuration(Database& db, uint16 instance_id, uint32_t new_duration) { auto results = db.QueryDatabase( fmt::format( - "UPDATE `{}` SET `duration` = {} WHERE `{}` = {}", + "UPDATE `{}` SET `duration` = {}, `expire_at` = (`duration` + `start_time`) WHERE `{}` = {}", TableName(), new_duration, PrimaryKey(), @@ -65,7 +27,7 @@ public: auto results = db.QueryDatabase( fmt::format( SQL( - SELECT ((start_time + duration) - UNIX_TIMESTAMP()) AS `remaining` FROM `{}` + SELECT (`expire_at` - UNIX_TIMESTAMP()) AS `remaining` FROM `{}` WHERE `id` = {} ), TableName(), diff --git a/common/ruletypes.h b/common/ruletypes.h index b2a5594b0..ff061c5f8 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -1096,6 +1096,7 @@ RULE_CATEGORY(Instances) RULE_INT(Instances, ReservedInstances, 100, "Number of instance IDs which are reserved for globals. This value should not be changed while a server is running") RULE_BOOL(Instances, RecycleInstanceIds, true, "Setting whether free instance IDs should be recycled to prevent them from gradually running out at 32k") RULE_INT(Instances, GuildHallExpirationDays, 90, "Amount of days before a Guild Hall instance expires") +RULE_INT(Instances, ExpireOffsetTimeSeconds, 3600, "Amount of seconds to beyond instance expiration time we wait to purge the entry from the database. (Default: 1 Hour)") RULE_CATEGORY_END() RULE_CATEGORY(Expedition) diff --git a/common/version.h b/common/version.h index a86ce925d..d5de4e012 100644 --- a/common/version.h +++ b/common/version.h @@ -42,7 +42,7 @@ * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9320 +#define CURRENT_BINARY_DATABASE_VERSION 9321 #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054 #endif diff --git a/world/dynamic_zone.cpp b/world/dynamic_zone.cpp index 0b60b17ec..ba53b58d0 100644 --- a/world/dynamic_zone.cpp +++ b/world/dynamic_zone.cpp @@ -137,8 +137,11 @@ void DynamicZone::SetSecondsRemaining(uint32_t seconds_remaining) m_expire_time = now + new_remaining; m_duration = std::chrono::duration_cast(m_expire_time - m_start_time); - InstanceListRepository::UpdateDuration(database, - GetInstanceID(), static_cast(m_duration.count())); + InstanceListRepository::UpdateDuration( + database, + GetInstanceID(), + static_cast(m_duration.count()) + ); SendZonesDurationUpdate(); // update zone caches and actual instance's timer } diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index bc2244d78..8491cac2a 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -3506,6 +3506,7 @@ void QuestManager::UpdateInstanceTimer(uint16 instance_id, uint32 new_duration) e.duration = new_duration; e.start_time = std::time(nullptr); + e.expire_at = e.start_time + e.duration; const int updated = InstanceListRepository::UpdateOne(database, e);