[Scheduler] Event scheduler implementation (#1257)

* Event scheduler implementation

* Create 2021_02_17_server_scheduled_events.sql

* Tweak

* Remove unused event [skip ci]

* Cleanup [skip ci]

* PR adjustments

* Database manifest
This commit is contained in:
Chris Miles
2021-03-29 02:52:57 -05:00
committed by GitHub
parent f51bc4daaf
commit 7aa5308f9c
31 changed files with 2053 additions and 50 deletions
+2
View File
@@ -152,6 +152,7 @@ SET(zone_sources
zone.cpp
zone_config.cpp
zonedb.cpp
zone_event_scheduler.cpp
zone_reload.cpp
zone_store.cpp
zoning.cpp)
@@ -265,6 +266,7 @@ SET(zone_headers
worldserver.h
xtargetautohaters.h
zone.h
zone_event_scheduler.h
zone_config.h
zonedb.h
zonedump.h
+8 -3
View File
@@ -78,6 +78,7 @@
#include <pthread.h>
#include "../common/unix.h"
#include "zone_store.h"
#include "zone_event_scheduler.h"
#endif
@@ -100,8 +101,9 @@ QueryServ *QServ = 0;
TaskManager *task_manager = 0;
NpcScaleManager *npc_scale_manager;
QuestParserCollection *parse = 0;
EQEmuLogSys LogSys;
WorldContentService content_service;
EQEmuLogSys LogSys;
ZoneEventScheduler event_scheduler;
WorldContentService content_service;
const SPDat_Spell_Struct* spells;
int32 SPDAT_RECORDS = -1;
const ZoneConfig *Config;
@@ -387,6 +389,8 @@ int main(int argc, char** argv) {
ZoneStore::LoadContentFlags();
event_scheduler.SetDatabase(&database)->LoadScheduledEvents();
#ifdef BOTS
LogInfo("Loading bot commands");
int botretval = bot_command_init();
@@ -430,6 +434,7 @@ int main(int argc, char** argv) {
parse->ReloadQuests();
worldserver.Connect();
worldserver.SetScheduler(&event_scheduler);
Timer InterserverTimer(INTERSERVER_TIMER); // does MySQL pings and auto-reconnect
#ifdef EQPROFILE
@@ -541,11 +546,11 @@ int main(int argc, char** argv) {
entity_list.CorpseProcess();
entity_list.TrapProcess();
entity_list.RaidProcess();
entity_list.Process();
entity_list.MobProcess();
entity_list.BeaconProcess();
entity_list.EncounterProcess();
event_scheduler.Process(zone, &content_service);
if (zone) {
if (!zone->Process()) {
+19 -1
View File
@@ -55,7 +55,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "zone_config.h"
#include "zone_reload.h"
extern EntityList entity_list;
extern Zone* zone;
extern volatile bool is_zone_loaded;
@@ -2815,6 +2814,15 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
break;
}
case ServerOP_UpdateSchedulerEvents: {
LogScheduler("Received signal from world to update");
if (m_zone_scheduler) {
m_zone_scheduler->LoadScheduledEvents();
}
break;
}
case ServerOP_HotReloadQuests:
{
if (!zone) {
@@ -3301,3 +3309,13 @@ void WorldServer::OnKeepAlive(EQ::Timer *t)
ServerPacket pack(ServerOP_KeepAlive, 0);
SendPacket(&pack);
}
ZoneEventScheduler *WorldServer::GetScheduler() const
{
return m_zone_scheduler;
}
void WorldServer::SetScheduler(ZoneEventScheduler *scheduler)
{
WorldServer::m_zone_scheduler = scheduler;
}
+6
View File
@@ -20,6 +20,7 @@
#include "../common/eq_packet_structs.h"
#include "../common/net/servertalk_client_connection.h"
#include "zone_event_scheduler.h"
class ServerPacket;
class EQApplicationPacket;
@@ -76,6 +77,11 @@ private:
std::unique_ptr<EQ::Net::ServertalkClient> m_connection;
std::unique_ptr<EQ::Timer> m_keepalive;
ZoneEventScheduler *m_zone_scheduler;
public:
ZoneEventScheduler *GetScheduler() const;
void SetScheduler(ZoneEventScheduler *scheduler);
};
#endif
+2 -15
View File
@@ -941,7 +941,6 @@ Zone::Zone(uint32 in_zoneid, uint32 in_instanceid, const char* in_short_name)
spawn2_timer(1000),
hot_reload_timer(1000),
qglobal_purge_timer(30000),
hotzone_timer(120000),
m_SafePoint(0.0f,0.0f,0.0f),
m_Graveyard(0.0f,0.0f,0.0f,0.0f)
{
@@ -1582,8 +1581,6 @@ bool Zone::Process() {
}
}
if(hotzone_timer.Check()) { UpdateHotzone(); }
mMovementManager->Process();
return true;
@@ -2602,19 +2599,9 @@ uint32 Zone::GetSpawnKillCount(uint32 in_spawnid) {
return 0;
}
void Zone::UpdateHotzone()
void Zone::SetIsHotzone(bool is_hotzone)
{
std::string query = StringFormat("SELECT hotzone FROM zone WHERE short_name = '%s'", GetShortName());
auto results = content_db.QueryDatabase(query);
if (!results.Success())
return;
if (results.RowCount() == 0)
return;
auto row = results.begin();
is_hotzone = atoi(row[0]) == 0 ? false: true;
Zone::is_hotzone = is_hotzone;
}
void Zone::RequestUCSServerStatus() {
+1 -2
View File
@@ -289,7 +289,6 @@ public:
void SpawnConditionChanged(const SpawnCondition &c, int16 old_value);
void SpawnStatus(Mob *client);
void StartShutdownTimer(uint32 set_time = (RuleI(Zone, AutoShutdownDelay)));
void UpdateHotzone();
void UpdateQGlobal(uint32 qid, QGlobal newGlobal);
void weatherSend(Client *client = nullptr);
@@ -356,6 +355,7 @@ public:
*/
void mod_init();
void mod_repop();
void SetIsHotzone(bool is_hotzone);
private:
bool allow_mercs;
@@ -401,7 +401,6 @@ private:
Timer *Weather_Timer;
Timer autoshutdown_timer;
Timer clientauth_timer;
Timer hotzone_timer;
Timer initgrids_timer;
Timer qglobal_purge_timer;
ZoneSpellsBlocked *blocked_spells;
+165
View File
@@ -0,0 +1,165 @@
#include "zone_event_scheduler.h"
#include "../common/rulesys.h"
void ZoneEventScheduler::Process(Zone *zone, WorldContentService *content_service)
{
std::time_t time = std::time(nullptr);
std::tm *now = std::localtime(&time);
// once a minute polling
if (m_last_polled_minute != now->tm_min) {
int month = (now->tm_mon + 1);
int year = (now->tm_year + 1900);
LogSchedulerDetail(
"Polling year [{}] month [{}] day [{}] hour [{}] minute [{}]",
year,
month,
now->tm_mday,
now->tm_hour,
now->tm_min
);
// because stored active events could have a reference of time that has been changed since
// the time has been updated, we need to make sure we update internal fields so that
// the scheduler can properly end events if we set a new end date
SyncEventDataWithActiveEvents();
// active events
for (auto &e: m_active_events) {
LogSchedulerDetail("Looping active event [{}]", e.description);
// if event becomes no longer active
if (!ValidateEventReadyToActivate(e)) {
LogSchedulerDetail("Looping active event validated [{}]", e.event_type);
if (e.event_type == ServerEvents::EVENT_TYPE_HOT_ZONE_ACTIVE) {
LogScheduler("Deactivating event [{}] disabling hotzone status", e.description);
for (auto &short_name: split(e.event_data, ',')) {
if (zone->GetShortName() == short_name) {
zone->SetIsHotzone(false);
break;
}
}
RemoveActiveEvent(e);
}
if (e.event_type == ServerEvents::EVENT_TYPE_RULE_CHANGE) {
LogScheduler("Deactivating event [{}] resetting rules to normal", e.description);
RuleManager::Instance()->LoadRules(m_database, RuleManager::Instance()->GetActiveRuleset(), true);
// force active events clear and reapply all active events because we reset the entire state
// ideally if we could revert only the state of which was originally set we would only remove one active event
m_active_events.clear();
}
if (e.event_type == ServerEvents::EVENT_TYPE_CONTENT_FLAG_CHANGE) {
auto flag_name = e.event_data;
if (!flag_name.empty()) {
LogScheduler("Deactivating event [{}] resetting content flags", e.description);
content_service->ReloadContentFlags(*m_database);
}
// force active events clear and reapply all active events because we reset the entire state
// ideally if we could revert only the state of which was originally set we would only remove one active event
m_active_events.clear();
}
}
}
// check for active
for (auto &e: m_events) {
// discard uninteresting events as its less work to calculate time on events we don't care about
// different processes are interested in different events
if (
e.event_type != ServerEvents::EVENT_TYPE_HOT_ZONE_ACTIVE &&
e.event_type != ServerEvents::EVENT_TYPE_CONTENT_FLAG_CHANGE &&
e.event_type != ServerEvents::EVENT_TYPE_RULE_CHANGE
) {
continue;
}
// the scheduler as of today manipulates events in memory and is preferred to be that way
// the scheduler changes temporary "state" in the server for a period of time for things such as
// hotzone activation, content flag activation, rule value activation
// when these events expire, the events become untoggled in memory
// there can be support for one-time events that are more suitable to run from worlds scheduler
// such as broadcasts, reloads
if (ValidateEventReadyToActivate(e) && !IsEventActive(e)) {
if (e.event_type == ServerEvents::EVENT_TYPE_HOT_ZONE_ACTIVE) {
for (auto &short_name: split(e.event_data, ',')) {
if (zone->GetShortName() == short_name) {
zone->SetIsHotzone(true);
LogScheduler("Activating Event [{}] Enabling zone as hotzone", e.description);
break;
}
}
m_active_events.push_back(e);
}
if (e.event_type == ServerEvents::EVENT_TYPE_RULE_CHANGE) {
auto params = split(e.event_data, '=');
auto rule_key = params[0];
auto rule_value = params[1];
if (!rule_key.empty() && !rule_value.empty()) {
LogScheduler(
"Activating Event [{}] scheduled rule change, setting rule [{}] to [{}]",
e.description,
rule_key,
rule_value
);
RuleManager::Instance()->SetRule(rule_key.c_str(), rule_value.c_str(), nullptr, false, true);
}
m_active_events.push_back(e);
}
if (e.event_type == ServerEvents::EVENT_TYPE_CONTENT_FLAG_CHANGE) {
auto flag_name = e.event_data;
if (!flag_name.empty()) {
LogScheduler(
"Activating Event [{}] scheduled content flag change, setting flag [{}] to enabled",
e.description,
flag_name
);
auto flags = content_service->GetContentFlags();
flags.push_back(flag_name);
content_service->SetContentFlags(flags);
m_active_events.push_back(e);
}
}
}
}
m_last_polled_minute = now->tm_min;
}
}
// because stored active events could have a reference of time that has been changed since
// the time has been updated, we need to make sure we update internal fields so that
// the scheduler can properly end events if we set a new end date
void ZoneEventScheduler::SyncEventDataWithActiveEvents()
{
for (auto &a: m_active_events) {
for (auto &e: m_events) {
if (e.id == a.id) {
a.description = e.description;
a.event_type = e.event_type;
a.event_data = e.event_data;
a.minute_start = e.minute_start;
a.hour_start = e.hour_start;
a.day_start = e.day_start;
a.month_start = e.month_start;
a.year_start = e.year_start;
a.minute_end = e.minute_end;
a.hour_end = e.hour_end;
a.day_end = e.day_end;
a.month_end = e.month_end;
a.year_end = e.year_end;
a.cron_expression = e.cron_expression;
a.created_at = e.created_at;
a.deleted_at = e.deleted_at;
}
}
}
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef EQEMU_ZONE_EVENT_SCHEDULER_H
#define EQEMU_ZONE_EVENT_SCHEDULER_H
#include "../common/server_event_scheduler.h"
#include "zone.h"
#include "../common/content/world_content_service.h"
class ZoneEventScheduler : public ServerEventScheduler {
public:
void Process(Zone *zone, WorldContentService *content_service);
void SyncEventDataWithActiveEvents();
};
#endif //EQEMU_ZONE_EVENT_SCHEDULER_H
+1 -14
View File
@@ -146,20 +146,7 @@ ZoneRepository::Zone ZoneStore::GetZone(const char *in_zone_name)
*/
void ZoneStore::LoadContentFlags()
{
std::vector<std::string> set_content_flags;
auto content_flags = ContentFlagsRepository::GetWhere(database, "enabled = 1");
set_content_flags.reserve(content_flags.size());
for (auto &flags: content_flags) {
set_content_flags.push_back(flags.flag_name);
}
LogInfo(
"Enabled content flags [{}]",
implode(", ", set_content_flags)
);
content_service.SetContentFlags(set_content_flags);
content_service.ReloadContentFlags(database);
}
/**