UnrealScript library and basis for all Acedia Framework mods
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.
 

296 lines
10 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 OnCreated()
{
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();
}
}
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);
}
}
/**
* 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.")
}