diff --git a/sources/Feature.uc b/sources/Feature.uc index 34f12c3..5d3d65e 100644 --- a/sources/Feature.uc +++ b/sources/Feature.uc @@ -3,9 +3,12 @@ * can be enabled or disabled, according to server owner's wishes. * In the current version of Acedia enabling or disabling a feature requires * manually editing configuration file and restarting a server. - * Factually feature is just a collection of settings with one universal - * 'isActive' setting that tells Acedia whether or not to load a feature. - * Copyright 2019 Anton Tarasenko + * Creating a `Feature` instance should be done by using + * `EnableMe()` / `DisableMe()` methods; instead of regular `Constructor()` + * and `Finalizer()` one should use `OnEnabled() and `OnDisabled()` methods. + * Any instances created through other means will be automatically deallocated, + * enforcing `Singleton`-like behavior for the `Feature` class. + * Copyright 2019 - 2021 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -22,9 +25,21 @@ * You should have received a copy of the GNU General Public License * along with Acedia. If not, see . */ -class Feature extends Singleton +class Feature extends AcediaObject abstract; +// Default value of this variable will store one and only existing version +// of `Feature` of this class. +var private Feature activeInstance; +var private int activeInstanceLifeVersion; + +// Setting default value of this variable to 'true' prevents creation of +// a `Feature`, even if no instances of it exist. This is used to ensure active +// `Feature`s can only be created through the proper means and behave like +// singletons. +// Only a default value is ever used. +var protected bool blockSpawning; + // Setting that tells Acedia whether or not to enable this feature // during initialization. // Only it's default value is ever used. @@ -33,56 +48,127 @@ var private config bool autoEnable; // Listeners listed here will be automatically activated. var public const array< class > requiredListeners; -// Sets whether to enable this feature by default. -public static final function SetAutoEnable(bool doEnable) +protected function Contructor() { - default.autoEnable = doEnable; - StaticSaveConfig(); + if (default.blockSpawning) + { + FreeSelf(); + return; + } + SetListenersActiveSatus(true); + OnEnabled(); } +protected function Finalizer() +{ + if (GetInstance() == self) + { + SetListenersActiveSatus(false); + OnDisabled(); + default.activeInstance = none; + } +} + +/** + * Returns an instance of the `Feature` of the class used to call + * this method. + * + * @return Active `Feature` instance of the class used to call + * this method (i.e. `class'MyFunFeature'.static.GetInstance()`). + * `none` if particular `Feature` in question is not currently active. + */ +public final static function Feature GetInstance() +{ + if (default.activeInstance == none) { + return none; + } + if ( default.activeInstance.GetLifeVersion() + != default.activeInstanceLifeVersion) + { + default.activeInstance = none; + return none; + } + return default.activeInstance; +} + +/** + * Checks if caller `Feature` should be auto-enabled on game starting. + * + * @return `true` if caller `Feature` should be auto-enabled and + * `false` otherwise. + */ public static final function bool IsAutoEnabled() { return default.autoEnable; } -// Whether feature is enabled is determined by +/** + * Checks whether caller `Feature` is currently enabled. + * + * @return `true` if caller `Feature` is currently enabled and + * `false` otherwise. + */ public static final function bool IsEnabled() { return (GetInstance() != none); } -// Enables feature of given class. +/** + * Enables the feature and returns it's active instance. + * + * Cannot fail. Any checks on whether it's appropriate to enable `Feature` + * must be done separately, before calling this method. + * + * @return Active instance of the caller `Feature` class. + */ public static final function Feature EnableMe() { local Feature newInstance; - if (IsEnabled()) - { - return Feature(GetInstance()); + if (IsEnabled()) { + return GetInstance(); } default.blockSpawning = false; - // TODO: code duplication with `Service`? newInstance = Feature(__().memory.Allocate(default.class)); + default.activeInstance = newInstance; + default.activeInstanceLifeVersion = newInstance.GetLifeVersion(); default.blockSpawning = true; return newInstance; } +/** + * Disables this feature in case it is enabled. Does nothing otherwise. + * + * @return `true` if `Feature` in question was enabled at th moment of + * the call and `false` otherwise. + */ public static final function bool DisableMe() { local Feature myself; - myself = Feature(GetInstance()); + myself = GetInstance(); if (myself != none) { - myself.Destroy(); + myself.FreeSelf(); return true; } return false; } -// Event functions that are called when +/** + * When using proper methods for enabling a `Feature`, + * this method is guaranteed to be called right after it is enabled. + * + * AVOID MANUALLY CALLING IT. + */ protected function OnEnabled(){} + +/** + * When using proper methods for enabling a `Feature`, + * this method is guaranteed to be called right after it is disabled. + * + * AVOID MANUALLY CALLING IT. + */ protected function OnDisabled(){} -// Set listeners' status private static function SetListenersActiveSatus(bool newStatus) { local int i; @@ -93,25 +179,8 @@ private static function SetListenersActiveSatus(bool newStatus) } } -protected function OnCreated() -{ - default.blockSpawning = true; - SetListenersActiveSatus(true); - OnEnabled(); -} - -protected function OnDestroyed() -{ - SetListenersActiveSatus(false); - OnDisabled(); -} - defaultproperties { autoEnable = false - DrawType = DT_None - // Prevent spawning this feature by any other means than 'EnableMe()'. blockSpawning = true - // Features are server-only actors - remoteRole = ROLE_None } \ No newline at end of file