Object problem
When working with UnrealScript one can distinguish between following types of variables:
- Value types:
bool
,byte
,int
,float
,string
and anystruct
; Actor
s: objects that have Actor as their parent;- Non-actor
Object
s: 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 insideObject
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 Object
s, 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 Object
s.
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 Object
s and not Actor
s?
Using Actor
instead of Object
s 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 Actor
s is
a viable alternative.
We've made decision to use Object
s because:
- They are more lightweight than
Actor
s; Actor
s are linked to a certain level, butObject
s are not: you needs an instance of anotherActor
to start spawning ones of your own, while you can simply use a large chunk of Acedia's functionality anywhere;- There are ways to completely solve all the above-mentioned problem in the future. Secret ways for now.