Object problem

When working with UnrealScript one can distinguish between following types of variables:

  1. Value types: bool, byte, int, float, string and any struct;
  2. Actors: objects that have Actor as their parent;
  3. Non-actor Objects: object of any class not derived derived from the Actor.

Most of the mods mainly use first and second type, but we make heavy use of the third one. This allows Acedia to provide convenient interfaces for its functionality and simplify implementation of its features. However it also creates several new problems, normally not encountered by other mods.

What are the problems?

Storing Actor references inside Objects is dangerous

Storing actors in non-actor objects is a bad idea and can lead to game/server crashes. If you are interested in the explanation of why, you can read discussion here. This isn't really a problem in most mutators, since they store references to actors (KFMonster, KFPlayerController, ...) inside other actors (Mutator, GameType, ...). However, in Acedia almost everything is a non-actor object, so simply having actor variables can be volatile: even a simple check myActor != none can lead to a crash under some circumstances if myActor was destroyed recently enough.

Acedia's end goal is to provide you with enough object-based API and wrappers, that you don't have to reference actors directly. We are a long way away from that, so for whenever our API is not enough, we also provide a safer way to work with actors inside objects (see boxing for more info).

Objects are effectively indestructible

We'll illustrate this point with Text - Acedia's own type that is used as a replacement for string. Consider following simple code:

function MyFunction()
{
    local string message;
    message = "My log message";
    Log(message);
}

For Acedia's Text an equivalent code would be:

function MyFunction()
{
    local Text message;
    message = _.text.FromString("My log message");
    _.logger.Info(message); //  Just Acedia's logging, kind of like `Log()`
}

There is an additional action of calling FromString() to create a new Text object, but otherwise logic is the same. But there is one crucial difference: unlike string value, Text is an object that will continue to exists in memory even after we exit MyFunction()'s body: every single call to MyFunction() will keep creating new objects that won't ever be used anywhere else.

Supposed way to deal with this is garbage collection, but it is a very expensive operation in Unreal Engines before their 3rd version. For example, the lag at the start of each wave in Killing Floor is caused by a garbage collection call. Many players hate it and several mods were made to disable it, since there is usually not much to actually clean up.

NOTE: One can wonder, is there really that much objects to clean up with Acedia? Answer is that there is way more than without Acedia and, while you might be fine even without any cleanup, ignoring it completely isn't scalable and can blow up in our face in the future.

This means that Acedia needed to find another way of dealing with issue of creating useless objects. That solution is releasing objects:

function MyFunction()
{
    local Text message;
    message = _.text.FromString("My log message");
    _.logger.Info(message);
    message.FreeSelf(); //  `_.memory.Free(message)` would also work
}

Here FreeSelf() call marks message as an unneeded object, making it available to be reused. In fact, if you call new MyFunction() several times in a row:

MyFunction()
MyFunction()
//  Paste a couple thousand more calls here
MyFunction()

and all of the calls will use only one Text object - the exactly same as the one first call has created. This concerns not only Text, but almost every single Acedia's object. To efficiently use Acedia, you must learn to deallocate objects that are not going to be used anymore.

And then it got worse

The serious problem here is that after calling message.FreeSelf() one really should not use message reference anymore. If Text variable from above wasn't local, but class variable, then we'd have to add one more instruction message = none:

var Text message;

function MyFunction()
{
    message = _.text.FromString("My log message");
    _.logger.Info(message);
    message.FreeSelf();
    message = none; // Forget about `message`!
}

Deallocating a message does not make an actual object go away and, without setting message variable to none, you risk continuing to use it; however, some other piece of code might re-allocate that object and use it for something completely different. This means unpredictable and undefined behavior for everybody. To avoid creating this problem everyone must always make sure to forget about objects they released by setting their references to none.

Just the possibility of that happening is a huge problem, since messing this up will create bugs that are extremely hard to catch. Possibility of such bugs is the biggest downside of using Acedia.

IMPORTANT: If you did encounter a situation where certain object exhibits an unpredictable behavior and you suspect that it is due to someone else messing up and using released objects - you can attempt to identify the culprit via GetReferencers() method defined inside Object class.

Why risk that new problem?

Why risk new problems if other mods already do completely fine job? To put it simply: because it allowed us to create a more powerful and, overall, easier to use library. To our knowledge, no other Killing Floor mod attempted to produce a library of the Acedia's scale. This isn't to say that there wasn't any huge mods, but their changes, for the most part, didn't deviate too much from what could've been directly achieved by already provided functionality of UnrealScript.

Acedia, on the other hand, introduces collections, databases, richer text support (including parsing and JSON functionality), Signal/Slot event system, custom loggers, aliases, etc.. A lot of those couldn't even be implemented without using Objects, while others would be awkward to use. Short term any of the existing Acedia functionality can be reimplemented in a simpler way, without abuse of Objects. However having AcediaCore the way it is now simplifies making future functionality and makes possible certain future plans we aren't yet ready to talk about.

Why Objects and not Actors?

Using Actor instead of Objects seems to resolve all of the above problems. So why not use them instead? This is a completely legitimate question and for all we know using Actors is a viable alternative. We've made decision to use Objects because:

  1. They are more lightweight than Actors;
  2. Actors are linked to a certain level, but Objects are not: you needs an instance of another Actor to start spawning ones of your own, while you can simply use a large chunk of Acedia's functionality anywhere;
  3. There are ways to completely solve all the above-mentioned problem in the future. Secret ways for now.