Browse Source

Refactor init system

Squashed commit of the following:

commit f7cbf54045
Author: Anton Tarasenko <dkanus@gmail.com>
Date:   Thu Jun 23 02:45:20 2022 +0700

    Done

commit 3cf67a3ca5
Author: Anton Tarasenko <dkanus@gmail.com>
Date:   Wed Jun 22 20:49:28 2022 +0700

    Almost done

commit 6dfb9dc204
Author: Anton Tarasenko <dkanus@gmail.com>
Date:   Wed Jun 22 17:03:06 2022 +0700

    Now it even works

commit 2be4656f51
Author: Anton Tarasenko <dkanus@gmail.com>
Date:   Wed Jun 22 16:30:35 2022 +0700

    Kind of compiles now

commit 73914e9b7e
Author: Anton Tarasenko <dkanus@gmail.com>
Date:   Tue Jun 21 04:24:04 2022 +0700

    Daily dirty commit
pull/8/head
Anton Tarasenko 2 years ago
parent
commit
b44c7e2ca8
  1. 2
      sources/Avarice/AvariceAPI.uc
  2. 4
      sources/Commands/BuiltInCommands/ACommandHelp.uc
  3. 34
      sources/Commands/Commands_Feature.uc
  4. 2
      sources/Config/AcediaConfig.uc
  5. 471
      sources/CoreRealm/AcediaEnvironment.uc
  6. 38
      sources/CoreRealm/Events/Environment_FeatureDisabled_Signal.uc
  7. 40
      sources/CoreRealm/Events/Environment_FeatureDisabled_Slot.uc
  8. 38
      sources/CoreRealm/Events/Environment_FeatureEnabled_Signal.uc
  9. 40
      sources/CoreRealm/Events/Environment_FeatureEnabled_Slot.uc
  10. 64
      sources/CoreRealm/Global.uc
  11. 107
      sources/CoreRealm/LevelCore.uc
  12. 3
      sources/CoreRealm/_manifest.uc
  13. 307
      sources/CoreService.uc
  14. 249
      sources/Features/Feature.uc
  15. 4
      sources/InfoQueryHandler/InfoQueryHandler.uc
  16. 3
      sources/Manifest.uc
  17. 4
      sources/Memory/MemoryAPI.uc
  18. 31
      sources/ServerLevelCore.uc
  19. 2
      sources/Types/AcediaActor.uc
  20. 2
      sources/Types/AcediaObject.uc
  21. 4
      sources/Unreal/BroadcastsAPI/BroadcastAPI.uc
  22. 16
      sources/Unreal/UnrealAPI.uc

2
sources/Avarice/AvariceAPI.uc

@ -31,7 +31,7 @@ public final function array<AvariceLink> GetAllLinks()
local Avarice_Feature avariceFeature;
local array<AvariceLink> emptyResult;
avariceFeature =
Avarice_Feature(class'Avarice_Feature'.static.GetInstance());
Avarice_Feature(class'Avarice_Feature'.static.GetEnabledInstance());
if (avariceFeature != none) {
return avariceFeature.GetAllLinks();
}

4
sources/Commands/BuiltInCommands/ACommandHelp.uc

@ -72,7 +72,7 @@ private final function DisplayCommandList(EPlayer player)
local Commands_Feature commandsFeature;
if (player == none) return;
commandsFeature =
Commands_Feature(class'Commands_Feature'.static.GetInstance());
Commands_Feature(class'Commands_Feature'.static.GetEnabledInstance());
if (commandsFeature == none) return;
console = player.BorrowConsole();
@ -101,7 +101,7 @@ private final function DisplayCommandHelpPages(
local Commands_Feature commandsFeature;
if (player == none) return;
commandsFeature =
Commands_Feature(class'Commands_Feature'.static.GetInstance());
Commands_Feature(class'Commands_Feature'.static.GetEnabledInstance());
if (commandsFeature == none) return;
// If arguments were empty - at least display our own help page

34
sources/Commands/Commands_Feature.uc

@ -58,12 +58,6 @@ protected function OnEnabled()
commandDelimiters[2] = P("[");
// Negation of the selector
commandDelimiters[3] = P("!");
// `SwapConfig()` will no longer touch `_.unreal.mutator.OnMutate(self)`
// with `emergencyEnabledMutate` set to `true`, so we need to give
// access here
if (emergencyEnabledMutate) {
_.unreal.mutator.OnMutate(self).connect = HandleMutate;
}
}
protected function OnDisabled()
@ -90,6 +84,7 @@ protected function OnDisabled()
protected function SwapConfig(FeatureConfig config)
{
local Commands newConfig;
newConfig = Commands(config);
if (newConfig == none) {
return;
@ -132,13 +127,14 @@ public final static function EmergencyEnable()
{
local Text autoConfig;
local Commands_Feature feature;
if (!IsEnabled())
{
autoConfig = GetAutoEnabledConfig();
EnableMe(autoConfig);
__().memory.Free(autoConfig);
}
feature = Commands_Feature(GetInstance());
feature = Commands_Feature(GetEnabledInstance());
if ( !feature.emergencyEnabledMutate
&& !feature.IsUsingMutateInput() && !feature.IsUsingChatInput())
{
@ -159,7 +155,8 @@ public final static function EmergencyEnable()
public final static function bool IsUsingChatInput()
{
local Commands_Feature instance;
instance = Commands_Feature(GetInstance());
instance = Commands_Feature(GetEnabledInstance());
if (instance != none) {
return instance.useChatInput;
}
@ -177,7 +174,8 @@ public final static function bool IsUsingChatInput()
public final static function bool IsUsingMutateInput()
{
local Commands_Feature instance;
instance = Commands_Feature(GetInstance());
instance = Commands_Feature(GetEnabledInstance());
if (instance != none) {
return instance.useMutateInput;
}
@ -194,7 +192,8 @@ public final static function bool IsUsingMutateInput()
public final static function Text GetChatPrefix()
{
local Commands_Feature instance;
instance = Commands_Feature(GetInstance());
instance = Commands_Feature(GetEnabledInstance());
if (instance != none && instance.chatCommandPrefix != none) {
return instance.chatCommandPrefix.Copy();
}
@ -214,6 +213,7 @@ public final function RegisterCommand(class<Command> commandClass)
{
local Text commandName;
local Command newCommandInstance, existingCommandInstance;
if (commandClass == none) return;
if (registeredCommands == none) return;
@ -252,6 +252,7 @@ public final function RemoveCommand(class<Command> commandClass)
local Command nextCommand;
local Text nextCommandName;
local array<Text> keysToRemove;
if (commandClass == none) return;
if (registeredCommands == none) return;
@ -282,8 +283,10 @@ public final function Command GetCommand(BaseText commandName)
{
local Text commandNameLowerCase;
local Command commandInstance;
if (commandName == none) return none;
if (registeredCommands == none) return none;
commandNameLowerCase = commandName.LowerCopy();
commandInstance = Command(registeredCommands.GetItem(commandNameLowerCase));
commandNameLowerCase.FreeSelf();
@ -301,8 +304,10 @@ public final function array<Text> GetCommandNames()
local array<AcediaObject> keys;
local Text nextKeyAsText;
local array<Text> keysAsText;
if (registeredCommands == none) return keysAsText;
if (registeredCommands == none) {
return keysAsText;
}
keys = registeredCommands.GetKeys();
for (i = 0; i < keys.length; i += 1)
{
@ -327,6 +332,7 @@ public final function HandleInput(Parser parser, EPlayer callerPlayer)
local Command commandInstance;
local Command.CallData callData;
local MutableText commandName;
if (parser == none) return;
if (!parser.Ok()) return;
@ -347,6 +353,7 @@ private function bool HandleCommands(
bool teamMessage)
{
local Parser parser;
// We are only interested in messages that start with `chatCommandPrefix`
parser = _.text.Parse(message);
if (!parser.Match(chatCommandPrefix).Ok())
@ -364,6 +371,11 @@ private function HandleMutate(string command, PlayerController sendingPlayer)
{
local Parser parser;
local EPlayer sender;
// Ignore just "help", since a lot of other mutators use it
if (command ~= "help") {
return;
}
parser = _.text.ParseString(command);
sender = _.players.FromController(sendingPlayer);
HandleInput(parser, sender);

2
sources/Config/AcediaConfig.uc

@ -110,8 +110,6 @@ public static function Initialize()
if (default.existingConfigs != none) {
return;
}
CoreService(class'CoreService'.static.GetInstance())
._registerObjectClass(default.class);
default.existingConfigs = __().collections.EmptyAssociativeArray();
names = GetPerObjectNames( default.configName, string(default.class.name),
MaxInt);

471
sources/CoreRealm/AcediaEnvironment.uc

@ -0,0 +1,471 @@
/**
* Container for the information about available resources from other packages.
* Copyright 2022 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 <https://www.gnu.org/licenses/>.
*/
class AcediaEnvironment extends AcediaObject;
/**
* # `AcediaEnvironment`
*
* Instance of this class will be used by Acedia to manage resources available
* from different packages like `Feature`s, aliases, etc..
* This is mostly necessary to implement Acedia loader (and, possibly,
* its alternatives) that would load available packages and enable `Feature`s
* admin wants to be enabled.
*
* ## Packages
*
* Any package to be used in Acedia should first be *registered* with
* `RegisterPackage()` method. Then a manifest class from it will be read and
* Acedia will become aware of all the resources that package contains.
* Once any of those resources is used, package gets marked as *loaded* and its
* *entry object* (if specified) will be created.
*
* ## `Feature`s
*
* Whether `Feature` is enabled is governed by the `AcediaEnvironment` added
* into the `Global` class. It is possible to create several `Feature`
* instances of the same class instance of each class, but only one can be
* considered enabled at the same time.
*/
var private array< class<_manifest> > availablePackages;
var private array< class<_manifest> > loadedPackages;
var private array< class<Feature> > availableFeatures;
var private array<Feature> enabledFeatures;
var private array<int> enabledFeaturesLifeVersions;
var private string manifestSuffix;
var private LoggerAPI.Definition infoRegisteringPackage, infoAlreadyRegistered;
var private LoggerAPI.Definition errNotRegistered, errFeatureAlreadyEnabled;
var private LoggerAPI.Definition warnFeatureAlreadyEnabled;
var private LoggerAPI.Definition errFeatureClassAlreadyEnabled;
var private Environment_FeatureEnabled_Signal onFeatureEnabledSignal;
var private Environment_FeatureDisabled_Signal onFeatureDisabledSignal;
/**
* Signal that will be emitted when new `Feature` is enabled.
* Emitted after `Feature`'s `OnEnabled()` method was called.
*
* [Signature]
* void <slot>(Feature enabledFeature)
*
* @param enabledFeature `Feature` instance that was just enabled.
*/
/* SIGNAL */
public final function Environment_FeatureEnabled_Slot OnFeatureEnabled(
AcediaObject receiver)
{
return Environment_FeatureEnabled_Slot(
onFeatureEnabledSignal.NewSlot(receiver));
}
/**
* Signal that will be emitted when new `Feature` is disabled.
* Emitted after `Feature`'s `OnDisabled()` method was called.
*
* [Signature]
* void <slot>(class<Feature> disabledFeatureClass)
*
* @param disabledFeatureClass Class of the `Feature` instance that was
* just disabled.
*/
/* SIGNAL */
public final function Environment_FeatureDisabled_Slot OnFeatureDisabled(
AcediaObject receiver)
{
return Environment_FeatureDisabled_Slot(
onFeatureEnabledSignal.NewSlot(receiver));
}
protected function Constructor()
{
// Always register our core package
RegisterPackage_S("AcediaCore");
onFeatureEnabledSignal = Environment_FeatureEnabled_Signal(
_.memory.Allocate(class'Environment_FeatureEnabled_Signal'));
onFeatureDisabledSignal = Environment_FeatureDisabled_Signal(
_.memory.Allocate(class'Environment_FeatureDisabled_Signal'));
}
protected function Finalizer()
{
_.memory.Free(onFeatureEnabledSignal);
_.memory.Free(onFeatureDisabledSignal);
}
/**
* Registers an Acedia package with name given by `packageName`.
*
* @param packageName Name of the package to register. Must not be `none`.
* This package must exist and not have yet been registered in this
* environment.
* @return `true` if package was successfully registered, `false` if it
* either does not exist, was already registered or `packageName` is
* `none`.
*/
public final function bool RegisterPackage(BaseText packageName)
{
local class<_manifest> manifestClass;
if (packageName == none) {
return false;
}
_.logger.Auto(infoRegisteringPackage).Arg(packageName.Copy());
manifestClass = class<_manifest>(DynamicLoadObject(
packageName.ToString() $ manifestSuffix, class'Class', true));
if (manifestClass == none)
{
_.logger.Auto(errNotRegistered).Arg(packageName.Copy());
return false;
}
if (IsManifestRegistered(manifestClass))
{
_.logger.Auto(infoAlreadyRegistered).Arg(packageName.Copy());
return false;
}
availablePackages[availablePackages.length] = manifestClass;
ReadManifest(manifestClass);
return true;
}
/**
* Registers an Acedia package with name given by `packageName`.
*
* @param packageName Name of the package to register.
* This package must exist and not have yet been registered in this
* environment.
* @return `true` if package was successfully registered, `false` if it
* either does not exist or was already registered.
*/
public final function RegisterPackage_S(string packageName)
{
local Text wrapper;
wrapper = _.text.FromString(packageName);
RegisterPackage(wrapper);
_.memory.Free(wrapper);
}
private final function bool IsManifestRegistered(class<_manifest> manifestClass)
{
local int i;
for (i = 0; i < availablePackages.length; i += 1)
{
if (manifestClass == availablePackages[i]) {
return true;
}
}
return false;
}
private final function ReadManifest(class<_manifest> manifestClass)
{
local int i;
for (i = 0; i < manifestClass.default.aliasSources.length; i += 1)
{
if (manifestClass.default.aliasSources[i] == none) {
continue;
}
_.memory.Allocate(manifestClass.default.aliasSources[i]);
}
for (i = 0; i < manifestClass.default.features.length; i += 1)
{
if (manifestClass.default.features[i] == none) {
continue;
}
manifestClass.default.features[i].static.LoadConfigs();
availableFeatures[availableFeatures.length] =
manifestClass.default.features[i];
}
for (i = 0; i < manifestClass.default.testCases.length; i += 1)
{
class'TestingService'.static
.RegisterTestCase(manifestClass.default.testCases[i]);
}
}
/**
* Returns all packages registered in the caller `AcediaEnvironment`.
*
* NOTE: package being registered doesn't mean it's actually loaded.
* Package must either be explicitly loaded or automatically when one of its
* resources is being used.
*
* @return All packages registered in caller `AcediaEnvironment`.
*/
public final function array< class<_manifest> > GetAvailablePackages()
{
return availablePackages;
}
/**
* Returns all packages loaded in the caller `AcediaEnvironment`.
*
* NOTE: package being registered doesn't mean it's actually loaded.
* Package must either be explicitly loaded or automatically when one of its
* resources is being used.
*
* @return All packages loaded in caller `AcediaEnvironment`.
*/
public final function array< class<_manifest> > GetLoadedPackages()
{
return loadedPackages;
}
/**
* Returns all `Feature`s available in the caller `AcediaEnvironment`.
*
* @return All `Feature`s available in the caller `AcediaEnvironment`.
*/
public final function array< class<Feature> > GetAvailableFeatures()
{
return availableFeatures;
}
/**
* Returns all `Feature` instances enabled in the caller `AcediaEnvironment`.
*
* @return All `Feature`s enabled in the caller `AcediaEnvironment`.
*/
public final function array<Feature> GetEnabledFeatures()
{
local int i;
for (i = 0; i < enabledFeatures.length; i += 1) {
enabledFeatures[i].NewRef();
}
return enabledFeatures;
}
// CleanRemove `Feature`s that got deallocated.
// This shouldn't happen unless someone messes up.
private final function CleanEnabledFeatures()
{
local int i;
while (i < enabledFeatures.length)
{
if ( enabledFeatures[i].GetLifeVersion()
!= enabledFeaturesLifeVersions[i])
{
enabledFeatures.Remove(i, 1);
}
else {
i += 1;
}
}
}
/**
* Checks if `Feature` of given class `featureClass` is enabled.
*
* NOTE: even if If feature of class `featureClass` is enabled, it's not
* necessarily that the instance you have reference to is enabled.
* Although unlikely, it is possible that someone spawned another instance
* of the same class that isn't considered enabled. If you want to check
* whether some particular instance of given class `featureClass` is enabled,
* use `IsFeatureEnabled()` method instead.
*
* @param featureClass Feature class to check for being enabled.
* @return `true` if feature of class `featureClass` is currently enabled and
* `false` otherwise.
*/
public final function bool IsFeatureClassEnabled(class<Feature> featureClass)
{
local int i;
if (featureClass == none) {
return false;
}
CleanEnabledFeatures();
for (i = 0; i < enabledFeatures.length; i += 1)
{
if (featureClass == enabledFeatures[i].class) {
return true;
}
}
return false;
}
/**
* Checks if given `Feature` instance is enabled.
*
* If you want to check if any instance instance of given class
* `classToCheck` is enabled (and not `feature` specifically), use
* `IsFeatureClassEnabled()` method instead.
*
* @param feature Feature instance to check for being enabled.
* @return `true` if feature `feature` is currently enabled and
* `false` otherwise.
*/
public final function bool IsFeatureEnabled(Feature feature)
{
local int i;
if (feature == none) return false;
if (!feature.IsAllocated()) return false;
CleanEnabledFeatures();
for (i = 0; i < enabledFeatures.length; i += 1)
{
if (feature == enabledFeatures[i]) {
return true;
}
}
return false;
}
/**
* Returns enabled `Feature` instance of the given class `featureClass`.
*
* @param featureClass Feature class to find enabled instance for.
* @return Enabled `Feature` instance of the given class `featureClass`.
* If no feature of `featureClass` is enabled, returns `none`.
*/
public final function Feature GetEnabledFeature(class<Feature> featureClass)
{
local int i;
if (featureClass == none) {
return none;
}
CleanEnabledFeatures();
for (i = 0; i < enabledFeatures.length; i += 1)
{
if (featureClass == enabledFeatures[i].class)
{
enabledFeatures[i].NewRef();
return enabledFeatures[i];
}
}
return none;
}
/**
* Enables given `Feature` instance `newEnabledFeature` with a given config.
*
* @see `Feature::EnableMe()`.
*
* @param newEnabledFeature Instance to enable.
* @param configName Name of the config to enable `newEnabledFeature`
* feature with. `none` means "default" config (will be created, if
* necessary).
* @return `true` if given `newEnabledFeature` was enabled and `false`
* otherwise (including if feature of the same class has already been
* enabled).
*/
public final function bool EnableFeature(
Feature newEnabledFeature,
BaseText configName)
{
local int i;
if (newEnabledFeature == none) return false;
if (!newEnabledFeature.IsAllocated()) return false;
CleanEnabledFeatures();
for (i = 0; i < enabledFeatures.length; i += 1)
{
if (newEnabledFeature.class == enabledFeatures[i].class)
{
if (newEnabledFeature == enabledFeatures[i])
{
_.logger
.Auto(warnFeatureAlreadyEnabled)
.Arg(_.text.FromClass(newEnabledFeature.class));
}
else
{
_.logger
.Auto(errFeatureClassAlreadyEnabled)
.Arg(_.text.FromClass(newEnabledFeature.class));
}
return false;
}
}
newEnabledFeature.NewRef();
enabledFeatures[enabledFeatures.length] = newEnabledFeature;
enabledFeaturesLifeVersions[enabledFeaturesLifeVersions.length] =
newEnabledFeature.GetLifeVersion();
newEnabledFeature.EnableInternal(configName);
onFeatureEnabledSignal.Emit(newEnabledFeature);
return true;
}
/**
* Disables given `Feature` instance `featureToDisable`.
*
* @see `Feature::EnableMe()`.
*
* @param featureToDisable Instance to disable.
* @return `true` if given `newEnabledFeature` was disabled and `false`
* otherwise (including if it already was disabled).
*/
public final function bool DisableFeature(Feature featureToDisable)
{
local int i;
if (featureToDisable == none) return false;
if (!featureToDisable.IsAllocated()) return false;
CleanEnabledFeatures();
for (i = 0; i < enabledFeatures.length; i += 1)
{
if (featureToDisable == enabledFeatures[i])
{
enabledFeatures.Remove(i, 1);
enabledFeaturesLifeVersions.Remove(i, 1);
featureToDisable.DisableInternal();
onFeatureDisabledSignal.Emit(featureToDisable.class);
_.memory.Free(featureToDisable);
}
return true;
}
return false;
}
/**
* Disables all currently enabled `Feature`s.
*
* Mainly intended for the clean up when Acedia shuts down.
*/
public final function DisableAllFeatures()
{
local int i;
local array<Feature> featuresCopy;
CleanEnabledFeatures();
featuresCopy = enabledFeatures;
enabledFeatures.length = 0;
enabledFeaturesLifeVersions.length = 0;
for (i = 0; i < enabledFeatures.length; i += 1)
{
featuresCopy[i].DisableInternal();
onFeatureDisabledSignal.Emit(featuresCopy[i].class);
}
_.memory.FreeMany(featuresCopy);
}
defaultproperties
{
manifestSuffix = ".Manifest"
infoRegisteringPackage = (l=LOG_Info,m="Registering package \"%1\".")
infoAlreadyRegistered = (l=LOG_Info,m="Package \"%1\" is already registered.")
errNotRegistered = (l=LOG_Error,m="Package \"%2\" has failed to be registered.")
warnFeatureAlreadyEnabled = (l=LOG_Warning,m="Same instance of `Feature` class `%1` is already enabled.")
errFeatureClassAlreadyEnabled = (l=LOG_Error,m="Different instance of the same `Feature` class `%1` is already enabled.")
}

38
sources/CoreRealm/Events/Environment_FeatureDisabled_Signal.uc

@ -0,0 +1,38 @@
/**
* Signal class for `AcediaEnvironment`'s `FeatureDisabled()` signal.
* Copyright 2022 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 <https://www.gnu.org/licenses/>.
*/
class Environment_FeatureDisabled_Signal extends Signal;
public final function Emit(class<Feature> disabledFeatureClass)
{
local Slot nextSlot;
StartIterating();
nextSlot = GetNextSlot();
while (nextSlot != none)
{
Environment_FeatureDisabled_Slot(nextSlot).connect(disabledFeatureClass);
nextSlot = GetNextSlot();
}
CleanEmptySlots();
}
defaultproperties
{
relatedSlotClass = class'Environment_FeatureDisabled_Slot'
}

40
sources/CoreRealm/Events/Environment_FeatureDisabled_Slot.uc

@ -0,0 +1,40 @@
/**
* Slot class for `AcediaEnvironment`'s `FeatureDisabled()` signal.
* Copyright 2022 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 <https://www.gnu.org/licenses/>.
*/
class Environment_FeatureDisabled_Slot extends Slot;
delegate connect(class<Feature> disabledFeatureClass)
{
DummyCall();
}
protected function Constructor()
{
connect = none;
}
protected function Finalizer()
{
super.Finalizer();
connect = none;
}
defaultproperties
{
}

38
sources/CoreRealm/Events/Environment_FeatureEnabled_Signal.uc

@ -0,0 +1,38 @@
/**
* Signal class for `AcediaEnvironment`'s `FeatureEnabled()` signal.
* Copyright 2022 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 <https://www.gnu.org/licenses/>.
*/
class Environment_FeatureEnabled_Signal extends Signal;
public final function Emit(Feature enabledFeature)
{
local Slot nextSlot;
StartIterating();
nextSlot = GetNextSlot();
while (nextSlot != none)
{
Environment_FeatureEnabled_Slot(nextSlot).connect(enabledFeature);
nextSlot = GetNextSlot();
}
CleanEmptySlots();
}
defaultproperties
{
relatedSlotClass = class'Environment_FeatureEnabled_Slot'
}

40
sources/CoreRealm/Events/Environment_FeatureEnabled_Slot.uc

@ -0,0 +1,40 @@
/**
* Slot class for `AcediaEnvironment`'s `FeatureEnabled()` signal.
* Copyright 2022 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 <https://www.gnu.org/licenses/>.
*/
class Environment_FeatureEnabled_Slot extends Slot;
delegate connect(Feature enabledFeature)
{
DummyCall();
}
protected function Constructor()
{
connect = none;
}
protected function Finalizer()
{
super.Finalizer();
connect = none;
}
defaultproperties
{
}

64
sources/Global.uc → sources/CoreRealm/Global.uc

@ -25,25 +25,27 @@ class Global extends Object;
// main instance in this variable's default value.
var protected Global myself;
var public RefAPI ref;
var public BoxAPI box;
var public LoggerAPI logger;
var public CollectionsAPI collections;
var public UnrealAPI unreal;
var public TimeAPI time;
var public AliasesAPI alias;
var public TextAPI text;
var public MemoryAPI memory;
var public ConsoleAPI console;
var public ChatAPI chat;
var public ColorAPI color;
var public UserAPI users;
var public PlayersAPI players;
var public JSONAPI json;
var public DBAPI db;
var public AvariceAPI avarice;
var public RefAPI ref;
var public BoxAPI box;
var public LoggerAPI logger;
var public CollectionsAPI collections;
var public UnrealAPI unreal;
var public TimeAPI time;
var public AliasesAPI alias;
var public TextAPI text;
var public MemoryAPI memory;
var public ConsoleAPI console;
var public ChatAPI chat;
var public ColorAPI color;
var public UserAPI users;
var public PlayersAPI players;
var public JSONAPI json;
var public DBAPI db;
var public AvariceAPI avarice;
var public KFFrontend kf;
var public AcediaEnvironment environment;
var public KFFrontend kf;
public final static function Global GetInstance()
{
@ -79,7 +81,22 @@ protected function Initialize()
db = DBAPI(memory.Allocate(class'DBAPI'));
avarice = AvariceAPI(memory.Allocate(class'AvariceAPI'));
kf = KFFrontend(memory.Allocate(class'KF1_Frontend'));
json.StaticConstructor();
environment = AcediaEnvironment(memory.Allocate(class'AcediaEnvironment'));
class'InfoQueryHandler'.static.StaticConstructor();
}
public final function bool ConnectServerLevelCore()
{
if (class'ServerLevelCore'.static.GetInstance() == none) {
return false;
}
class'UnrealService'.static.Require();
class'ConnectionService'.static.Require();
// TODO: this is hack as fuck, needs to be redone
unreal.mutator.OnMutate(
ServiceAnchor(memory.Allocate(class'ServiceAnchor')))
.connect = EnableCommandsFeature;
return true;
}
public function DropGameplayAPI()
@ -88,6 +105,15 @@ public function DropGameplayAPI()
kf = none;
}
private final function EnableCommandsFeature(
string command,
PlayerController sendingPlayer)
{
if (command ~= "acediacommands") {
class'Commands_Feature'.static.EmergencyEnable();
}
}
public function DropCoreAPI()
{
memory = none;

107
sources/CoreRealm/LevelCore.uc

@ -0,0 +1,107 @@
/**
* Provides Acedia with access to actors of a certain level (server, client or
* even entry level).
* Copyright 2022 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 <https://www.gnu.org/licenses/>.
*/
class LevelCore extends AcediaActor
abstract;
/**
* # `LevelCore`
*
* Since in UnrealScript mixing objects and actors (or, more precisely, storing
* `Actor`s in non-actor `Object`s) is dangerous, `LevelCore`s are needed to
* provide an access to a certain level and its `Actor`s.
*
* `LevelCore is a singleton, however this isn't a design choice, but
* a side-effect of granting `Object`s safe access to an `Actor` through class
* and `static` methods and `default` fields.
*
* ## Using `LevelCore`s
*
* `LevelCore` itself is `abstract` and cannot be instantiated, so to make use
* of it, child classes must be declared. This allows one to specialize
* `LevelCore`: for example, Acedia's `ServerLevelCore` checks that net mode of
* the level it is created on is either `NM_DedicatedServer` or
* `NM_ListenServer`.
* Then Acedia's server API can only be created and used with such a level.
*
* To create you own `LevelCore`, extend simply this base class.
* If you also want to specialize your level core, overload `CreateLevelCore()`
* method to add your checks.
*/
// Allows to force creation of `LevelCore` only through `CreateLevelCore()`
// method
var private bool blockSpawning;
// Default value of this variable will store one and only existing version
// `LevelCore`
var private LevelCore activeInstance;
protected function Finalizer()
{
default.activeInstance = none;
}
public static function LevelCore CreateLevelCore(Actor source)
{
if (GetInstance() != none) return none;
if (source == none) return none;
default.blockSpawning = false;
default.activeInstance = source.Spawn(default.class);
default.blockSpawning = true;
return default.activeInstance;
}
public final static function LevelCore GetInstance()
{
local bool instanceExists;
instanceExists = default.activeInstance != none
&& !default.activeInstance.bPendingDelete;
if (instanceExists) {
return default.activeInstance;
}
return none;
}
// Make sure only one instance of 'LevelCore' exists at any point in time.
event PreBeginPlay()
{
if (default.blockSpawning || GetInstance() != none)
{
Destroy();
return;
}
default.activeInstance = self;
super.PreBeginPlay();
}
// Clean up
event Destroyed()
{
if (self == default.activeInstance) {
default.activeInstance = none;
}
super.Destroyed();
}
defaultproperties
{
blockSpawning = true
}

3
sources/_manifest.uc → sources/CoreRealm/_manifest.uc

@ -30,9 +30,6 @@ var public const array< class<Feature> > features;
// List of test cases in this manifest's package.
var public const array< class<TestCase> > testCases;
// List of required services.
var public const array< class<Service> > services;
defaultproperties
{
}

307
sources/CoreService.uc

@ -1,307 +0,0 @@
/**
* Core service that is always running alongside Acedia framework, must be
* created by a launcher.
* Used for booting up and shutting down Acedia.
* Also used for spawning `Actor`s as the only must-have `Service`.
* Copyright 2020 - 2021 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 <https://www.gnu.org/licenses/>.
*/
class CoreService extends Service
dependson(BroadcastEventsObserver);
// Package's manifest is supposed to always have a name of
// "<package_name>.Manifest", this variable stores the ".Manifest" part
var private const string manifestSuffix;
// Classes that will need to do some cleaning before Acedia shuts down
var private array< class<AcediaObject> > usedObjectClasses;
var private array< class<AcediaActor> > usedActorClasses;
// `Singleton`s are handled as a special case and cleaned up after
// the rest of the classes.
var private array< class<Singleton> > usedSingletonClasses;
var private array< class<_manifest> > availableManifests;
var private array<string> packagesToLoad;
struct FeatureConfigPair
{
var public class<Feature> featureClass;
var public Text configName;
};
var private array<FeatureConfigPair> automaticConfigs;
var private array< class<Feature> > availableFeatures;
var private LoggerAPI.Definition infoLoadingPackage;
var private LoggerAPI.Definition infoBootingUp, infoBootingUpFinished;
var private LoggerAPI.Definition infoShuttingDown;
var private LoggerAPI.Definition errorNoManifest, errorCannotRunTests;
// We do not implement `OnShutdown()`, because total Acedia's clean up
// is supposed to happen before that event.
protected function OnLaunch()
{
BootUp();
default.packagesToLoad.length = 0;
}
/**
* Static method that starts everything needed by Acedia framework to function.
* Must be called before attempting to use any of the Acedia's functionality.
*
* Acedia needs to be able to spawn actors and for that it first needs to
* spawn `CoreService`. To make that possible you need to provide
* an `Actor` instance from current level. It can be any valid actor.
*
* @param source Valid actor instance that Acedia will use to
* spawn `CoreService`
* @param packages List of acedia packages to load.
* Using array of `string`s since Acedia's `Text` wouldn't yet
* be available.
*/
public final static function LaunchAcedia(
Actor source,
optional array<string> packages)
{
default.packagesToLoad = packages;
default.blockSpawning = false;
// Actual work will be done inside `BootUp()` private method that will be
// called from `OnCreated()` event.
source.Spawn(class'CoreService');
default.blockSpawning = true;
}
/**
* Shuts down Acedia, cleaning up created actors, default values,
* changes made to the standard game classes, etc..
*
* This method must be called before the level change (map change), otherwise
* Acedia is not guaranteed to work on the next map and you might
* even experience game crashes.
*/
public final function ShutdownAcedia()
{
local int i;
local AcediaActor nextActor;
local MemoryService memoryService;
_.logger.Auto(infoShuttingDown);
memoryService = MemoryService(class'MemoryService'.static.GetInstance());
// Turn off gameplay-related stuff first
class'Global'.static.GetInstance().DropGameplayAPI();
// Get rid of actors
foreach AllActors(class'AcediaActor', nextActor)
{
if (nextActor == self) continue;
if (nextActor == memoryService) continue;
nextActor.Destroy();
}
// Clean all used classes, except for singletons
for (i = 0; i < usedObjectClasses.length; i += 1) {
usedObjectClasses[i].static._cleanup();
}
for (i = 0; i < usedActorClasses.length; i += 1) {
usedActorClasses[i].static._cleanup();
}
// Remove remaining objects
_.unreal.broadcasts.Remove(class'BroadcastEventsObserver');
memoryService.ClearAll();
// Finally clean up singletons
for (i = 0; i < usedSingletonClasses.length; i += 1) {
usedSingletonClasses[i].static._cleanup();
}
// Clean API
class'Global'.static.GetInstance().DropCoreAPI();
_ = none;
// Get rid of the `MemoryService` and `CoreService` last
memoryService.Destroy();
Destroy();
Log("Acedia has shut down.");
}
// Loads packages, injects broadcast handler and optionally runs tests
private final function BootUp()
{
local int i;
local Text nextPackageName;
local class<_manifest> nextManifest;
_.logger.Auto(infoBootingUp);
LoadManifest(class'AcediaCore.Manifest');
// Load packages
for (i = 0; i < packagesToLoad.length; i += 1)
{
nextPackageName = _.text.FromString(packagesToLoad[i]);
_.logger.Auto(infoLoadingPackage).Arg(nextPackageName.Copy());
nextManifest = LoadManifestClass(packagesToLoad[i]);
if (nextManifest == none)
{
_.logger.Auto(errorNoManifest).Arg(nextPackageName.Copy());
continue;
}
availableManifests[availableManifests.length] = nextManifest;
LoadManifest(nextManifest);
_.memory.Free(nextPackageName);
}
nextPackageName = none;
_.logger.Auto(infoBootingUpFinished);
// Other initialization
class'UnrealService'.static.Require();
if (class'TestingService'.default.runTestsOnStartUp) {
RunStartUpTests();
}
class'InfoQueryHandler'.static.StaticConstructor();
_.unreal.mutator.OnMutate(_self).connect = EnableCommandsFeature;
}
private final function LoadManifest(class<_manifest> manifestClass)
{
local int i;
local FeatureConfigPair nextPair;
for (i = 0; i < manifestClass.default.aliasSources.length; i += 1)
{
if (manifestClass.default.aliasSources[i] == none) continue;
_.memory.Allocate(manifestClass.default.aliasSources[i]);
}
LaunchManifestServices(manifestClass);
for (i = 0; i < manifestClass.default.features.length; i += 1)
{
if (manifestClass.default.features[i] == none) continue;
manifestClass.default.features[i].static.LoadConfigs();
nextPair.featureClass = manifestClass.default.features[i];
nextPair.configName = manifestClass.default.features[i].static
.GetAutoEnabledConfig();
automaticConfigs[automaticConfigs.length] = nextPair;
availableFeatures[availableFeatures.length] =
manifestClass.default.features[i];
}
for (i = 0; i < manifestClass.default.testCases.length; i += 1)
{
class'TestingService'.static
.RegisterTestCase(manifestClass.default.testCases[i]);
}
}
public final function array<FeatureConfigPair> GetAutoConfigurationInfo()
{
local int i;
local array<FeatureConfigPair> result;
for (i = 0; i < automaticConfigs.length; i += 1)
{
result[i] = automaticConfigs[i];
if (result[i].configName != none) {
result[i].configName = result[i].configName.Copy();
}
}
return result;
}
public final function array< class<Feature> > GetAvailableFeatures()
{
return availableFeatures;
}
private final function class<_manifest> LoadManifestClass(string packageName)
{
return class<_manifest>(DynamicLoadObject( packageName $ manifestSuffix,
class'Class', true));
}
private final function LaunchManifestServices(class<_manifest> manifestClass)
{
local int i;
for (i = 0; i < manifestClass.default.services.length; i += 1)
{
if (manifestClass.default.services[i] != none) {
manifestClass.default.services[i].static.Require();
}
}
}
private final function RunStartUpTests()
{
local TestingService testService;
testService = TestingService(class'TestingService'.static.Require());
testService.PrepareTests();
if (testService.filterTestsByName) {
testService.FilterByName(testService.requiredName);
}
if (testService.filterTestsByGroup) {
testService.FilterByGroup(testService.requiredGroup);
}
if (!testService.Run()) {
_.logger.Auto(errorCannotRunTests);
}
}
private final function EnableCommandsFeature(
string command,
PlayerController sendingPlayer)
{
if (command ~= "acediacommands") {
class'Commands_Feature'.static.EmergencyEnable();
}
}
/**
* Registers class derived from `AcediaObject` for clean up when
* Acedia shuts down.
*
* Does not check for duplicates.
*
* This is an internal function and should not be used outside of
* AcediaCore package.
*/
public final function _registerObjectClass(class<AcediaObject> classToClean)
{
if (classToClean != none) {
usedObjectClasses[usedObjectClasses.length] = classToClean;
}
}
/**
* Registers class derived from `AcediaActor` for clean up when
* Acedia shuts down.
*
* Does not check for duplicates.
*
* This is an internal function and should not be used outside of
* AcediaCore package.
*/
public final function _registerActorClass(class<AcediaActor> classToClean)
{
local class<Singleton> singletonClass;
if (classToClean == none) {
return;
}
singletonClass = class<Singleton>(classToClean);
if (singletonClass != none) {
usedSingletonClasses[usedSingletonClasses.length] = singletonClass;
}
else {
usedActorClasses[usedActorClasses.length] = classToClean;
}
}
defaultproperties
{
manifestSuffix = ".Manifest"
infoBootingUp = (l=LOG_Info,m="Initializing Acedia.")
infoBootingUpFinished = (l=LOG_Info,m="Acedia initialized.")
infoShuttingDown = (l=LOG_Info,m="Shutting down Acedia.")
infoLoadingPackage = (l=LOG_Info,m="Loading package \"%1\".")
errorNoManifest = (l=LOG_Error,m="Cannot load `Manifest` for package \"%1\". Check if it's missing or if its name is spelled incorrectly.")
errorCannotRunTests = (l=LOG_Error,m="Could not perform Acedia's tests.")
}

249
sources/Features/Feature.uc

@ -1,19 +1,8 @@
/**
* Feature represents a certain subset of Acedia's functionality that
* 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.
* 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.
* `Feature`s store their configuration in a different object
* `FeatureConfig`, that uses per-object-config and allows users to define
* several different versions of `Feature`'s settings. Each `Feature` must be
* in 1-to-1 relationship with one sub-class of `FeatureConfig`, that should be
* defined in `configClass` variable.
* Copyright 2019 - 2021 Anton Tarasenko
* Features are intended as a replacement for mutators: a certain subset of
* functionality that can be enabled or disabled, according to server owner's
* wishes.
* Copyright 2019 - 2022 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
@ -33,83 +22,126 @@
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;
/**
* # `Feature`
*
* This class is Acedia's replacement for `Mutators`: a certain subset of
* functionality that can be enabled or disabled, according to server owner's
* wishes. Unlike `Mutator`s:
* * There is not limit for the amount of `Feature`s that can be active
* at the same time;
* * They also provide built-in ability to have several different configs
* that can be swapped during the runtime;
* * They can be enabled / disabled during the runtime.
* Achieving these points currently comes at the cost of developer having to
* perform additional work.
*
* ## Enabling `Feature`
*
* 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.
* There is nothing preventing you from allocating several more instances,
* however only one `Feature` per its class can be considered "enabled" at
* the same time. This is governed by `AcediaEnvironment` residing in
* the acting `Global` class.
*
* ## Configuration
*
* `Feature`s store their configuration in a different object
* `FeatureConfig`, that uses per-object-config and allows users to define
* several different versions of `Feature`'s settings. Each `Feature` must be
* in 1-to-1 relationship with one sub-class of `FeatureConfig`, that should be
* defined in `configClass` variable.
*
* ## Creating new `Feature` classes
*
* To create a new `Feature` one need:
* 1. Create child class for `Feature` (usual naming scheme
* "MyAwesomeThing_Feature") and a child class for feature config
* `FeatureConfig` (usual naming scheme is simply "MyAwesomeThing" to
* make config files more readable) and link them by setting
* `configClass` variable in `defaultproperties` in your `Feature`
* child class.
* 2. Properly setup `FeatureConfig` (read more in its own documentation);
* 3. Define `OnEnabled()` / `OnDisabled()` / `SwapConfig()` methods in
* a way that accounts for the possibility of them running during
* the gameplay (meaning that this must be possible - it can still be
* considered a heavy operation and it is allowed to cause lag).
* NOTE: `SwapConfig()` is always called just before `OnEnabled()` to
* set initial configuration up, so the bulk of `Feature` configuration
* can be done there.
* 4. Implement whatever it is your `Feature` will be doing.
*/
// Variables that store name and data from the config object that was
// chosen for this `Feature`.
// Data is expected to be in format that allows for JSON deserialization
// (see `JSONAPI.IsCompatible()` for details).
var private Text currentConfigName;
var private AssociativeArray currentConfig;
// Remembers if `EnableMe()` was called to indicate that `DisableMe()`
// should be called.
var private bool wasEnabled;
// Variable that store name of the config object that was chosen for this
// `Feature`.
var private Text currentConfigName;
// Class of this `Feature`'s config objects. Classes must be in 1-to-1
// correspondence.
// Class of this `Feature`'s config objects.
// These classes must be in 1-to-1 correspondence.
var public const class<FeatureConfig> configClass;
// 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;
// TODO: remove this?
// Setting that tells Acedia whether or not to enable this feature
// during initialization.
// Only it's default value is ever used.
var private config bool autoEnable;
// `Service` that will be launched and shut down along with this `Feature`.
// One should never launch or shut down this service manually.
// One should never launch or shut down this `Service` manually.
var protected const class<FeatureService> serviceClass;
var private string defaultConfigName;
var private LoggerAPI.Definition errorBadConfigData;
/**
* Loads all configs defined for the caller `Feature`'s class into internal
* collections.
*
* This method must be called only once, by initialization routines.
*/
public static final function LoadConfigs()
const defaultConfigName = "default";
protected function Finalizer()
{
if (default.configClass != none) {
default.configClass.static.Initialize();
}
DisableInternal();
_.memory.Free(currentConfigName);
currentConfigName = none;
}
protected function Constructor()
/**
* Calling this method for `Feature` instance that was added to the `Global`'s
* `AcediaEnvironment` will actually enable `Feature`, including calling
* `OnEnabled()` method. Otherwise this method will do nothing.
*
* This is internal method, it should not be called manually and neither will
* it do anything.
*
* @param newConfigName Config name to enable caller `Feature` with.
*/
public final /* internal */ function EnableInternal(BaseText newConfigName)
{
local FeatureService myService;
if (default.blockSpawning)
{
FreeSelf();
return;
}
if (wasEnabled) return;
if (!_.environment.IsFeatureEnabled(self)) return;
wasEnabled = true;
if (serviceClass != none) {
myService = FeatureService(serviceClass.static.Require());
}
if (myService != none) {
myService.SetOwnerFeature(self);
}
currentConfigName = none;
ApplyConfig(default.currentConfigName);
_.memory.Free(default.currentConfigName);
default.currentConfigName = none;
ApplyConfig(newConfigName);
OnEnabled();
}
protected function Finalizer()
/**
* Calling this for once enabled `Feature` instance that is no longer added to
* the active `Global`'s `AcediaEnvironment` will actually disable `Feature`,
* including calling `OnDisabled()` method. Otherwise this method will do
* nothing.
*
* This is internal method, it should not be called manually and neither will
* it do anything.
*/
public final /* internal */ function DisableInternal()
{
local FeatureService service;
if (GetInstance() != self) {
return;
}
if (!wasEnabled) return;
if (_.environment.IsFeatureEnabled(self)) return;
OnDisabled();
if (serviceClass != none) {
service = FeatureService(serviceClass.static.GetInstance());
@ -117,28 +149,33 @@ protected function Finalizer()
if (service != none) {
service.Destroy();
}
if (currentConfig != none) {
currentConfig.Empty(true);
}
_.memory.Free(currentConfigName);
_.memory.Free(currentConfig);
default.currentConfigName = none;
currentConfigName = none;
currentConfig = none;
default.activeInstance = none;
currentConfigName = none;
wasEnabled = false;
}
// TODO: free `newConfigName`?
/**
* Changes config for the caller `Feature` class.
* Loads all configs defined for the caller `Feature`'s class into internal
* collections.
*
* This method should only be called when caller `Feature` is enabled
* (allocated). To set initial config on this `Feature`'s start - specify it
* as a parameter to `EnableMe()` method.
* This method must be called only once, by initialization routines.
*/
public static final function LoadConfigs()
{
if (default.configClass != none) {
default.configClass.static.Initialize();
}
}
/**
* Changes config for the caller `Feature` class.
*
* Method will do nothing if `newConfigName` parameter is set to `none`.
* This method should only be called when caller `Feature` is enabled.
* To set initial config on this `Feature`'s start - specify it as a parameter
* to `EnableMe()` method.
*
* @param newConfigName Name of the config to apply to the caller `Feature`.
* If `none`, method will use "default" config, creating it if necessary.
*/
private final function ApplyConfig(BaseText newConfigName)
{
@ -168,12 +205,12 @@ private final function ApplyConfig(BaseText newConfigName)
* this method.
*
* @return Active `Feature` instance of the class used to call
* this method (i.e. `class'MyFunFeature'.static.GetInstance()`).
* this method (i.e. `class'MyFunFeature'.static.GetEnabledInstance()`).
* `none` if particular `Feature` in question is not currently active.
*/
public final static function Feature GetInstance()
public final static function Feature GetEnabledInstance()
{
return default.activeInstance;
return __().environment.GetEnabledFeature(default.class);
}
/**
@ -214,12 +251,16 @@ public static final function Text GetAutoEnabledConfig()
*/
public static final function Text GetCurrentConfig()
{
local Feature myInstance;
myInstance = GetInstance();
local Text configNameCopy;
local Feature myInstance;
myInstance = GetEnabledInstance();
if (myInstance == none) return none;
if (myInstance.currentConfigName == none) return none;
return myInstance.currentConfigName.Copy();
configNameCopy = myInstance.currentConfigName.Copy();
__().memory.Free(myInstance);
return configNameCopy;
}
/**
@ -230,15 +271,14 @@ public static final function Text GetCurrentConfig()
*/
public static final function bool IsEnabled()
{
return (GetInstance() != none);
return __().environment.IsFeatureClassEnabled(default.class);
}
/**
* Enables the feature and returns it's active instance.
*
* Cannot fail as long as `configName != none`. Any checks on whether it's
* appropriate to enable `Feature` must be done separately, before calling
* this method.
* Any checks on whether it's appropriate to enable `Feature` must be done
* separately, before calling this method.
*
* If `Feature` is already enabled - changes its config to `configName`.
*
@ -249,23 +289,14 @@ public static final function bool IsEnabled()
public static final function Feature EnableMe(BaseText configName)
{
local Feature myInstance;
myInstance = GetInstance();
myInstance = GetEnabledInstance();
if (myInstance != none)
{
myInstance.ApplyConfig(configName);
return myInstance;
}
if (configName != none) {
default.currentConfigName = configName.Copy();
}
else {
default.currentConfigName = none;
}
default.blockSpawning = false;
myInstance = Feature(__().memory.Allocate(default.class));
default.activeInstance = myInstance;
default.activeInstanceLifeVersion = myInstance.GetLifeVersion();
default.blockSpawning = true;
__().environment.EnableFeature(myInstance, configName);
return myInstance;
}
@ -277,11 +308,12 @@ public static final function Feature EnableMe(BaseText configName)
*/
public static final function bool DisableMe()
{
local Feature myself;
myself = GetInstance();
if (myself != none)
local Feature myInstance;
myInstance = GetEnabledInstance();
if (myInstance != none)
{
myself.FreeSelf();
__().environment.DisableFeature(myInstance);
__().memory.Free(myInstance);
return true;
}
return false;
@ -319,12 +351,7 @@ protected function SwapConfig(FeatureConfig newConfig){}
defaultproperties
{
autoEnable = false
blockSpawning = true
configClass = none
serviceClass = none
defaultConfigName = "default"
errorBadConfigData = (l=LOG_Error,m="Bad config value was provided for `%1`. Falling back to the \"default\".")
}

4
sources/InfoQueryHandler/InfoQueryHandler.uc

@ -288,11 +288,11 @@ defaultproperties
TACEDIA_HELP = 2
stringConstants(2) = "Acedia always supports four commands: {$TextEmphasis help}, {$TextEmphasis status}, {$TextEmphasis version} and {$TextEmphasis credits}"
TACEDIA_HELP_COMMANDS_CHAT = 3
stringConstants(3) = "To get detailed information about available to you commands, please type {$TextEmphasis %PREFIX%help -l} in chat"
stringConstants(3) = "To get detailed information about available to you commands, please type {$TextEmphasis %PREFIX%help} in chat"
TACEDIA_HELP_COMMANDS_CONSOLE = 4
stringConstants(4) = "To get detailed information about available to you commands, please type {$TextEmphasis mutate help -l} in console"
TACEDIA_HELP_COMMANDS_CHAT_AND_CONSOLE = 5
stringConstants(5) = "To get detailed information about available to you commands, please type {$TextEmphasis %PREFIX%help -l} in chat or {$TextEmphasis mutate help -l} in console"
stringConstants(5) = "To get detailed information about available to you commands, please type {$TextEmphasis %PREFIX%help} in chat or {$TextEmphasis mutate help -l} in console"
TACEDIA_HELP_COMMANDS_NO = 6
stringConstants(6) = "Unfortunately other commands aren't available right now. To enable them please type {$TextEmphasis mutate acediacommands} in console if you have enough rights to reenable them."
TACEDIA_HELP_COMMANDS_USELESS = 7

3
sources/Manifest.uc

@ -24,7 +24,6 @@ defaultproperties
{
features(0) = class'Commands_Feature'
features(1) = class'Avarice_Feature'
services(0) = class'ConnectionService'
aliasSources(0) = class'AliasSource'
aliasSources(1) = class'WeaponAliasSource'
aliasSources(2) = class'ColorAliasSource'
@ -42,7 +41,7 @@ defaultproperties
testCases(10) = class'TEST_Parser'
testCases(11) = class'TEST_JSON'
testCases(12) = class'TEST_TextCache'
testCases(13) = class'TEST_FormattedStrings'
testCases(13) = class'TEST_FormattedStrings'
testCases(14) = class'TEST_User'
testCases(15) = class'TEST_Memory'
testCases(16) = class'TEST_DynamicArray'

4
sources/Memory/MemoryAPI.uc

@ -104,7 +104,7 @@ public final function Object Allocate(
actorClassToAllocate = class<Actor>(classToAllocate);
if (actorClassToAllocate != none)
{
allocatedObject = class'CoreService'.static
allocatedObject = class'ServerLevelCore'.static
.GetInstance()
.Spawn(actorClassToAllocate);
}
@ -278,7 +278,7 @@ public final function CollectGarbage(optional bool keepAcediaPools)
}
}
// This makes Unreal Engine do garbage collection
class'CoreService'.static.GetInstance().ConsoleCommand("obj garbage");
class'ServerLevelCore'.static.GetInstance().ConsoleCommand("obj garbage");
}
defaultproperties

31
sources/ServerLevelCore.uc

@ -0,0 +1,31 @@
/**
* Copyright 2022 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 <https://www.gnu.org/licenses/>.
*/
class ServerLevelCore extends LevelCore;
public static function LevelCore CreateLevelCore(Actor source)
{
if (source == none) return none;
if (source.level.netMode != NM_DedicatedServer) return none;
return super.CreateLevelCore(source);
}
defaultproperties
{
}

2
sources/Types/AcediaActor.uc

@ -114,8 +114,6 @@ protected final static function bool StaticConstructorGuard()
if (!default._staticConstructorWasCalled)
{
default._staticConstructorWasCalled = true;
CoreService(class'CoreService'.static.GetInstance())
._registerActorClass(default.class);
return false;
}
return true;

2
sources/Types/AcediaObject.uc

@ -158,8 +158,6 @@ protected final static function bool StaticConstructorGuard()
if (!default._staticConstructorWasCalled)
{
default._staticConstructorWasCalled = true;
CoreService(class'CoreService'.static.GetInstance())
._registerObjectClass(default.class);
return false;
}
return true;

4
sources/Unreal/BroadcastsAPI/BroadcastAPI.uc

@ -282,7 +282,9 @@ public final function BroadcastHandler Add(
// I don't know why, I don't know when exactly, but not resetting it
// can lead to certain issues, including infinite recursion crashes.
class'BroadcastHandler'.default.nextBroadcastHandlerClass = none;
newBroadcastHandler = class'CoreService'.static.Require().Spawn(newBHClass);
newBroadcastHandler = class'ServerLevelCore'.static
.GetInstance()
.Spawn(newBHClass);
if (injectionLevel == BHIJ_Registered)
{
// There is guaranteed to be SOME broadcast handler

16
sources/Unreal/UnrealAPI.uc

@ -111,7 +111,7 @@ public final function SimpleSlot OnDestructionFor(
*/
public final function LevelInfo GetLevel()
{
return class'CoreService'.static.GetInstance().level;
return class'ServerLevelCore'.static.GetInstance().level;
}
/**
@ -123,7 +123,7 @@ public final function LevelInfo GetLevel()
*/
public final function GameReplicationInfo GetGameRI()
{
return class'CoreService'.static.GetInstance().level.GRI;
return class'ServerLevelCore'.static.GetInstance().level.GRI;
}
/**
@ -150,7 +150,7 @@ public final function KFGameReplicationInfo GetKFGameRI()
*/
public final function GameInfo GetGameType()
{
return class'CoreService'.static.GetInstance().level.game;
return class'ServerLevelCore'.static.GetInstance().level.game;
}
/**
@ -177,10 +177,10 @@ public final function KFGameType GetKFGameType()
*/
public final function Actor FindActorInstance(class<Actor> classToFind)
{
local Actor result;
local Service service;
service = class'CoreService'.static.Require();
foreach service.AllActors(classToFind, result)
local Actor result;
local LevelCore core;
core = class'ServerLevelCore'.static.GetInstance();
foreach core.AllActors(classToFind, result)
{
if (result != none) {
break;
@ -198,7 +198,7 @@ public final function Actor FindActorInstance(class<Actor> classToFind)
*/
public final function PlayerController GetLocalPlayer()
{
return class'CoreService'.static.GetInstance().level
return class'ServerLevelCore'.static.GetInstance().level
.GetLocalPlayerController();
}

Loading…
Cancel
Save