Browse Source
Squashed commit of the following: commitpull/8/headf7cbf54045
Author: Anton Tarasenko <dkanus@gmail.com> Date: Thu Jun 23 02:45:20 2022 +0700 Done commit3cf67a3ca5
Author: Anton Tarasenko <dkanus@gmail.com> Date: Wed Jun 22 20:49:28 2022 +0700 Almost done commit6dfb9dc204
Author: Anton Tarasenko <dkanus@gmail.com> Date: Wed Jun 22 17:03:06 2022 +0700 Now it even works commit2be4656f51
Author: Anton Tarasenko <dkanus@gmail.com> Date: Wed Jun 22 16:30:35 2022 +0700 Kind of compiles now commit73914e9b7e
Author: Anton Tarasenko <dkanus@gmail.com> Date: Tue Jun 21 04:24:04 2022 +0700 Daily dirty commit
Anton Tarasenko
2 years ago
22 changed files with 991 additions and 476 deletions
@ -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.") |
||||
} |
@ -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' |
||||
} |
@ -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 |
||||
{ |
||||
} |
@ -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' |
||||
} |
@ -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 |
||||
{ |
||||
} |
@ -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 |
||||
} |
@ -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.") |
||||
} |
@ -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 |
||||
{ |
||||
} |
Loading…
Reference in new issue