[Instances] Add expire_at Column (#4820)

* [Instances] Add `expire_at` Column

* expire_at update

* Update servertalk.h

* Add rule Instances:ExpireOffsetTimeSeconds
This commit is contained in:
Chris Miles 2025-03-30 14:46:02 -05:00 committed by GitHub
parent b9cfdea76c
commit 92128b98fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 62 additions and 68 deletions

View File

@ -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
},

View File

@ -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<uint32> &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);
}

View File

@ -58,6 +58,7 @@ uint32_t DynamicZoneBase::CreateInstance()
insert_instance.start_time = static_cast<int>(std::chrono::system_clock::to_time_t(m_start_time));
insert_instance.duration = static_cast<int>(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)

View File

@ -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<uint8_t>(strtoul(row[3], nullptr, 10)) : 0;
e.start_time = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.duration = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.never_expires = row[6] ? static_cast<uint8_t>(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<uint8_t>(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<uint8_t>(strtoul(row[3], nullptr, 10)) : 0;
e.start_time = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.duration = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.never_expires = row[6] ? static_cast<uint8_t>(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<uint8_t>(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<uint8_t>(strtoul(row[3], nullptr, 10)) : 0;
e.start_time = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.duration = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.never_expires = row[6] ? static_cast<uint8_t>(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<uint8_t>(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) + "'");

View File

@ -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(),

View File

@ -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)

View File

@ -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

View File

@ -137,8 +137,11 @@ void DynamicZone::SetSecondsRemaining(uint32_t seconds_remaining)
m_expire_time = now + new_remaining;
m_duration = std::chrono::duration_cast<std::chrono::seconds>(m_expire_time - m_start_time);
InstanceListRepository::UpdateDuration(database,
GetInstanceID(), static_cast<uint32_t>(m_duration.count()));
InstanceListRepository::UpdateDuration(
database,
GetInstanceID(),
static_cast<uint32_t>(m_duration.count())
);
SendZonesDurationUpdate(); // update zone caches and actual instance's timer
}

View File

@ -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);