diff --git a/sources/BaseAPI/API/Commands/CommandAPI.uc b/sources/BaseAPI/API/Commands/CommandAPI.uc index 8b46965..e31d2ec 100644 --- a/sources/BaseAPI/API/Commands/CommandAPI.uc +++ b/sources/BaseAPI/API/Commands/CommandAPI.uc @@ -21,11 +21,27 @@ */ class CommandAPI extends AcediaObject; +// Classes registered to be registered in async way +var private array< class > pendingClasses; +// Job that is supposed to register pending commands +var private CommandRegistrationJob registeringJob; + var private Commands_Feature commandsFeature; // DO NOT CALL MANUALLY -public final /*internal*/ function _reloadFeature() -{ +public final /*internal*/ function class _popPending() { + local class result; + + if (pendingClasses.length == 0) { + return none; + } + result = pendingClasses[0]; + pendingClasses.Remove(0, 1); + return result; +} + +// DO NOT CALL MANUALLY +public final /*internal*/ function _reloadFeature() { if (commandsFeature != none) { commandsFeature.FreeSelf(); commandsFeature = none; @@ -41,6 +57,8 @@ public final function bool AreCommandsEnabled() { /// Registers given command class, making it available via `Execute()`. /// +/// Unless you need command right now, it is recommended to use `RegisterCommandAsync()` instead. +/// /// Returns `true` if command was successfully registered and `false` otherwise`. /// /// # Errors @@ -55,6 +73,30 @@ public final function bool RegisterCommand(class commandClass) { return false; } +/// Registers given command class asynchronously, making it available via `Execute()`. +/// +/// Doesn't register commands immediately, instead scheduling it to be done at a later moment in +/// time, allowing. +/// This can help to reduce amount of work we do every tick during server startup, therefore +/// avoiding crashed due to the faulty infinite loop detection. +/// +/// # Errors +/// +/// If `commandClass` provides command with a name that is already taken (comparison is +/// case-insensitive) by a different command - a warning will be logged and newly passed +/// `commandClass` discarded. +public final function RegisterCommandAsync(class commandClass) { + if (commandsFeature == none) { + return; + } + pendingClasses[pendingClasses.length] = commandClass; + if (registeringJob == none || registeringJob.IsCompleted()) { + _.memory.Free(registeringJob); + registeringJob = CommandRegistrationJob(_.memory.Allocate(class'CommandRegistrationJob')); + _.scheduler.AddJob(registeringJob); + } +} + /// Removes command of given class from the list of registered commands. /// /// Removing once registered commands is not an action that is expected to be performed under normal diff --git a/sources/BaseAPI/API/Commands/CommandRegistrationJob.uc b/sources/BaseAPI/API/Commands/CommandRegistrationJob.uc new file mode 100644 index 0000000..5bc783c --- /dev/null +++ b/sources/BaseAPI/API/Commands/CommandRegistrationJob.uc @@ -0,0 +1,54 @@ +/** + * Author: dkanus + * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore + * License: GPL + * Copyright 2023 Anton Tarasenko + *------------------------------------------------------------------------------ + * This file is part of Acedia. + * + * Acedia is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, or + * (at your option) any later version. + * + * Acedia is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Acedia. If not, see . + */ +class CommandRegistrationJob extends SchedulerJob; + +var private class nextCommand; + +protected function Constructor() { + nextCommand = _.commands._popPending(); +} + +protected function Finalizer() { + nextCommand = none; +} + +public function bool IsCompleted() { + return (nextCommand == none); +} + +public function DoWork(int allottedWorkUnits) { + local int i, iterationsAmount; + + // Expected 300 units per tick, to register 20 commands per tick use about 10 + iterationsAmount = Max(allottedWorkUnits / 10, 1); + for (i = 0; i < iterationsAmount; i += 1) { + _.commands.RegisterCommand(nextCommand); + nextCommand = _.commands._popPending(); + if (nextCommand == none) { + break; + } + } +} + +defaultproperties +{ +} \ No newline at end of file