You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
307 lines
11 KiB
307 lines
11 KiB
/** |
|
* 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.") |
|
} |