Introduction
This "book" describes AcediaCore from the mod maker's perspective and is meant to give broad picture about AcediaCore's design and innerworkings, while also providing reasoning and motivations for why things are the way they are.
This document is not:
- A tutorial that provides quick and simple introduction into how to start using AcediaCore to create new mods;
- A collection of recipes for how to do certain specific things;
- A reference documentation that lists and describes every single class and method. The closest substitute for that would be AcediaCore's source code - most of the methods and classes are given brief descriptions in the comments. They might somewhat lack in quality, since having a peer review for them would not have been viable so far, but that is all we can offer. Any corrections to them are always welcome.
We assume that our audience is at least familiar with UnrealScript and right now we cannot recommend using AcediaCore to people new to the modding anyway: AcediaCore's API is not stable enough and has certain quirks that are unusual for a scripting language and can lead to some nasty, hard-to-catch bugs.
What the hell is Acedia Framework?
Acedia Framework is a set of packages that aims to augment what Killing Floor is from both gameplay and modding perspective. The ideal vision we're striving for is to create a framework that will be able to provide an ultimate Killing Floor experience, while doing only what server's admin wants: anything from a purely vanilla gameplay with some optional bug fixes to configuring the gameplay to the point of total conversion.
Acedia 0.1 was a small mutator that fixed game-breaking bugs and what Acedia is now might seem like a huge departure from that, but Acedia 0.1 was simply the first step in what we want to achieve. Still, we will take great care to ensure that people who only want critical vanilla bug fixes on top of otherwise untouched vanilla can still get exactly that with Acedia.
What Acedia was before is now broken into three different packages:
- AcediaCore - package can provides base Acedia classes and functionality, usable as a standalone library for other mods;
- AcediaLauncher - launcher that is supposed to load both native
Mutator
s and Acedia'sFeature
s. This is a soft dependency for using AcediaCore - highly recommended, but not absolutely necessary. It can also potentially be replaced by a custom launcher of your own making. - AcediaFixes - all the bug fixing
Feature
s were moved here. - Futility - package that provides rich set of server commands and
Feature
s to help with server administration. Actually direct gameplay changes will go into a separate package.
The topic of this document is only AcediaCore - a base class library, but other packages might be referenced as examples.
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.
AcediaObject
AcediaObject
is a base class for all objects in Acedia and it is meant to
enable: easy access to Acedia's API through globals, object allocation and
release, constructors and finalizers.
We'll go over each of these in detail.
Globals and Acedia's API
Globals (clases Global
, ServerGlobal
and ClientGlobal
) is our way of
solving the problem of adding an easy access to our new global functions.
Examples of global functions are Log()
, Caps()
, Abs()
, VSize()
and
a multitude of others that you can call from anywhere in UnrealScript.
They can be accessed from anywhere because they are all declared as static
methods inside Object - a base class for any other class.
Problem is, since we cannot add our own methods to the Object, then we also
can't add new global functions. The best we can do is declare new static methods
in our own classes, but calling them would be cumbersome:
class'glb'.static.DoIt()
.
Idea that we've used to solve this problem for Acedia is to provide every single
AcediaObject
with an instance of a class that would contain all our global
functions.
We can then save an instance of this class in a local variable _
, which would
allow us to simply write _.DoIt()
.
In actuality we don't just dump all of Acedia's global functions into one variable, but instead group them into a kind of namespaces:
_.text.FromString("I am here!"); // Text API
_.alias.ResolveColor_S("blue"); // Alias API
_.collections.EmptyArrayList(); // Collections API
_.memory.Allocate(class'SimpleSignal'); // Memory API
_
can't be accessed in static methods, since only default values are available
inside them.
Since writing default._
would also be bulky, AcediaObject
provides a static
method public static final function Global __()
that is always available:
__().text.FromString("I am here!");
__().alias.ResolveColor_S("blue");
__().collections.EmptyArrayList();
__().memory.Allocate(class'SimpleSignal');
Allocation and deallocation
The majority, if not all, of the Acedia's objects you will be using are going to
be created by specialized methods like _.text.FromString()
,
_.collections.EmptyArrayList()
or _.time.StartTimer()
and can be
deallocated with self.FreeSelf()
method.
However, if you want to allocate instances of your own classes, you'll need the
help of MemoryAPI
's methods: _.memory.Allocate()
and _.memory.Free()
.
Ultimately, all Acedia's objects must be created with _.memory.Allocate()
and
destroyed with _.memory.Free()
.
For example, here is how new Parser is created with _.text.NewParser()
:
public final function Parser NewParser()
{
return Parser(_.memory.Allocate(class'Parser'));
}
and self.FreeSelf()
is actually defined in AcediaObject as follows (ignore
parts about life versions for now, they will be explained in sections below):
public final function FreeSelf(optional int lifeVersion)
{
if (lifeVersion <= 0 || lifeVersion == GetLifeVersion()) {
_.memory.Free(self);
}
}
What is it exactly that Allocate()
and Free()
methods do?
Reference counting
Lets start with Free()
.
In earlier versions of Acedia, _.memory.Free()
/ self.FreeSelf()
methods
immediately deallocated provided AcediaObject
.
However that appeared to be a bad design decision, making it harder to
understand who was supposed to deallocate what objects and when.
Now calling _.memory.Free()
or self.FreeSelf()
does not necessarily
deallocate AcediaObject
, but simply tells it that you no longer store its
reference, reducing its internal reference count.
AcediaObject
is only deallocated once its reference count reaches zero.
Most objects are only used by whatever class allocated them and have reference
count of 1
throughout their lifetime.
However they become necessary when AcediaObject
s are stored somewhere else,
most common example being a collection:
local ArrayList myArray;
local Text myText;
myText = _.text.FromString("hello, world!");
// ^ here `myText` has reference count of 1
myArray = _.collections.EmptyArrayList().AddItem(myText)'
// ^ we've just put `myText` into collection,
// which increased its reference count to 2
myText.FreeSelf();
myText = none;
// ^ we no longer have a direct reference to that `Text`, but object lives on
// inside `myArray` collection with reference count of 1
myArray.RemoveIndex(0);
// ^ now we've also removed it from the collection, once again decreasing its
// reference count and causing it to get deallocated
Reference count isn't magically tracked by itself.
Just as we have to manually reduce it with _.memory.Free()
/ self.FreeSelf()
methods, to increase reference count we have to call self.NewRef()
method
whenever we want to store AcediaObject
instance in some place else.
In particular, Acedia's collections are doing it automatically when you add
objects inside them.
Who is responsible for releasing references?
For both historical reasons and what seemed like a good idea in practice,
two main rules for managing AcediaObject
s' references arose:
- If function returns an object (as a return value or as an
out
argument) - then whoever called that function is responsible for releasing its reference. If you've called_.text.Empty()
, then you must release theMutableText
object it has returned. Conversely, if you are implementing function that returns an object, then you lose the reference you've had: you need to either forget a reference to that object after returning it or, if you want to retain your reference, create a new reference withself.NewRef()
method first. - Functions do not release their arguments. If you pass an object as an argument to a function - you can expect that said object won't be release during function's execution. When implementing your own function - you should not release objects passed as its arguments.
However, these guidelines should be treated as default assumptions and not
hard rules.
First guideline can be sometimes broken for convenience.
For example, EPlayer
class has method BorrowConsole()
that returns
ConsoleWriter
for a quick access to player's console.
If we had to release returned object, then we'd have to do something like:
local ConsoleWriter writer;
writer = player.BorrowConsole();
writer.UseColorOnce(_.text.Green).WriteLine_S("All is fine!");
writer.FreeSelf();
However this particular method does not return you a new reference and expects
you to not release returned ConsoleWriter
object, making following code not
leak any memory:
player.BorrowConsole().WriteLine_S("All is fine!");
Moreover, another common scenario in which returned object should not be released is when a method returns the caller object itself to allow for method chaining:
player.BorrowConsole().UseColorOnce(_.text.Green).WriteLine_S("All is fine!");
Another example:
local int a, b, c;
local Parser parser;
parser = _.text.Parse_S("78 23 -34");
// Each call returns `parser`, but...
parser.MInteger(a).Skip().MInteger(b).Skip().MInteger(c);
// ...`parser` only needs to be released at the very end
parser.FreeSelf();
Such methods always specify that they return objects for method chaining.
Second guideline also has certain exceptions.
Obvious one is _.memory.Free()
that releases reference one passes to it.
Other notable ones are _.text.IntoString()
that releases BaseText
reference
passed to it, returning string
instead and Arg()
method in Logger
class
that always releases passed BaseText
reference for convenience's sake.
Such exceptions are relatively rare and always documented in the method's
description.
Methods that break first guideline also usually start with Borrow...
prefix
and, whenever possible, are made to be error-tolerant if you do release
returned reference.
Object pools
To reuse deallocated objects we need to store them somewhere, until they are
required again.
This idea isn't new and UnrealScript already tried to tackle issue of reusing
once created objects: class ObjectPool
stores unused objects (mostly resources
such as textures) inside dynamic array until they are needed.
Unfortunately, using a single ObjectPool for a large volume of objects is
impractical from performance perspective, since it stores objects of all
classes together and each object allocation from the pool can potentially
require going through the whole array:
// FILE: Engine/ObjectPool.uc
simulated function Object AllocateObject(class ObjectClass)
{
local Object Result;
local int ObjectIndex;
for(ObjectIndex = 0;ObjectIndex < Objects.Length;ObjectIndex++)
{
if(Objects[ObjectIndex].Class == ObjectClass)
{
Result = Objects[ObjectIndex];
Objects.Remove(ObjectIndex,1);
break;
}
}
if(Result == None)
Result = new(Outer) ObjectClass;
return Result;
}
For that reason Acedia uses a separate object pool (implemented by
AcediaObjectPool
) for every single class, making object reallocation as
trivial as grabbing the last stored object from AcediaObjectPool
's internal
dynamic array:
// From `AcediaObjectPool` sources
public final function AcediaObject Fetch()
{
local AcediaObject result;
if (storedClass == none) return none;
if (objectPool.length <= 0) return none;
result = objectPool[objectPool.length - 1];
objectPool.length = objectPool.length - 1;
return result;
}
New pool is automatically prepared for every class you create, as long as it is
inherited from AcediaObject
.
AcediaActors
do not use object pools and are to be simply Destroy()
ed.
Constructors and finalizers
Both AcediaObject
and AcediaActor
support
constructors
and
finalizers.
Constructor is a method that's called on object after it was created,
preparing it for use.
Finalizer is a method that's called when object is deallocated and can be used
to clean up any used resources.
NOTE: Technically, right now destructor might be a better terminology for Acedia's finalizers.
A good and simple example is from the ATradingComponent
that
allocates necessary objects inside its constructor and deallocates them in
its finalizer:
protected function Constructor()
{
onStartSignal = SimpleSignal(_.memory.Allocate(class'SimpleSignal'));
onEndSignal = SimpleSignal(_.memory.Allocate(class'SimpleSignal'));
onTraderSelectSignal = Trading_OnSelect_Signal(
_.memory.Allocate(class'Trading_OnSelect_Signal'));
}
protected function Finalizer()
{
_.memory.Free(onStartSignal);
_.memory.Free(onEndSignal);
_.memory.Free(onTraderSelectSignal);
onStartSignal = none;
onEndSignal = none;
onTraderSelectSignal = none;
}
Acedia's constructors do not take parameters and because of that some classes
also define Initialize()
method that is required to be used right after
an object was allocated.
Boxing
Boxing
is a process of turning value types such as bool
, byte
, int
or float
into objects.
The concept is very simple: to store a value as a reference type, we create a
box - an object that stores a single primitive value.
It could be implemented like that:
class MyBox extends Object;
var float value;
Since Acedia's boxes are immutable (their value cannot change once the box was created), they store their value in the private field and provide access to it through the appropriate getter method.
Boxes were introduced because they allowed creation of general collections:
Acedia's collections can only store AcediaObject
, but, thanks to boxing,
any value can be turned into an AcediaObject
and stored in the collection.
For native primitive types boxes can be created with either BoxAPI
or
manually:
local IntBox box1;
local FloatBox box2;
// Created with `BoxAPI`
box1 = _.box.int(7);
// Allocated and initialized manually
box2 = FloatBox(_.memory.Allocate(class'FloatBox'));
box2.Initialize(-2.48); // Must be done immediately after allocation!
// Works the same
Log("Int value:" @ box1.Get()); // Int value: 7
Log("Float value:" @ box2.Get()); // Float value: -2.48
Immutable boxes also have a counterpart - mutable references that also
provide Set()
method:
local IntRef ref1;
local FloatRef ref2;
// Created with `BoxAPI`
ref1 = _.ref.int(7);
// Allocated and initialized manually
ref2 = FloatRef(_.memory.Allocate(class'FloatRef'));
ref2.Initialize(-2.48); // Must be done immediately after allocation!
// Change values
ref1.Set(-89);
ref2.Set(0.56);
Log("Int value:" @ ref1.Get()); // Int value: -89
Log("Float value:" @ ref2.Get()); // Float value: 0.56
This makes it sound like reference are just more functional boxes!
But the guarantee of unchanged value has its own perks and the most important
difference between boxes and references concerns implementation of their
IsEqual()
and GetHash()
methods.
Let's talk about them a bit more.
Object equality and object hash
Comparing object variables with ==
operator checks reference equality:
whether variables refer to the exact same object.
But sometimes we want to implement value equality check - a comparison for
the contents of two objects, e.g. checking that two different Text
s
store the exact same data.
Acedia provides an alternative way to compare two objects - IsEqual()
method.
Its default implementation corresponds to that of ==
operator:
public function bool IsEqual(Object other)
{
return (self == other);
}
But it can be redefined, as long as it obeys following rules:
a.IsEqual(a) == true
;a.IsEqual(b)
if and only ifb.IsEqual(a)
;none
is only equal tonone
;- Result of
a.IsEqual(b)
does not change unless one of the objects gets deallocated.
Because of the last rule, IsEqual()
cannot compare two MutableText
s based on
their contents, since they can change without deallocation
(unlike contents of an immutable Text
).
Reimplementing IsEqual()
method also requires you to reimplement how object's
hash value is calculated.
Hash value is a an int
value associated with an object.
Equal objects must have the same hash value, but, while two different objects
are also allowed to share the same hash, such a collision should be highly
unlikely.
Essentially, object hash value should be 100% determined by that object's
contents, but behave as if it was a uniformly randomly generated int
.
This latter quality is necessary for
hash tables to efficiently function.
By default, Acedia's objects simply use randomly generated value, determined at
the moment of their allocation, as their hash (this means their hash value fully
depends on their reference).
This can be changed by reimplementing CalculateHashCode()
method.
Every object will only call it once to cache it for GetHashCode()
:
public final function int GetHashCode()
{
if (_hashCodeWasCached) {
return _cachedHashCode;
}
_hashCodeWasCached = true;
_cachedHashCode = CalculateHashCode();
return _cachedHashCode;
}
Important requirement placed on hash value is that it should never change for
an allocated object.
Unlike mutable objects, immutable ones, such as Text
, can benefit from
redefining their hash calculation to be fully dependent on their contents
instead of their reference:
protected function int CalculateHashCode()
{
local int i;
local int hash;
hash = 5381;
for (i = 0; i < codePoints.length; i += 1)
{
// hash * 33 + codePoints[i]
hash = ((hash << 5) + hash) + codePoints[i];
}
return hash;
}
This makes sure that two Text
s with equal contents have the same hash value
and that is what makes them usable as keys inside the HashTable
collection.
Equality and hashing for boxes and references
- Boxes redefine
IsEqual()
andGetHash()
to depend on the stored value. Since value inside the box cannot change, then there is no problem to base equality and hash on it. - References do not redefine
IsEqual()
/GetHash()
and behave like any other object - their hash is random and they are only equal to themselves.
local ByteBox box1, box2;
local ByteRef ref1, ref2;
box1 = _.box.byte(56);
box2 = _.box.byte(56);
ref1 = _.ref.byte(247);
ref2 = _.ref.byte(247);
// Boxes equality: true
Log("Boxes equality:" @ (box1.IsEqual(box2)));
// Boxes hash equality: true
Log("Boxes hash equality:" @ (box1.GetHash() == box2.GetHash()));
// Refs equality: false
Log("Refs equality:" @ (ref1.IsEqual(ref2)));
// Refs hash equality: false
// (that's the most likely result, but it can actually be `true` by pure chance)
Log("Refs hash equality:" @ (ref1.GetHash() == ref2.GetHash()));
NOTE: For
string
s the role of boxes and references is performed byText
andMutableText
classes that are discussed elsewhere.
Actor references with NativeActorRef
As was explained in object problem, storing actor references
directly inside objects is a bad idea.
The safe way to do it are special actor reference objects:
ActorRef
for Acedia's actors and NativeActorRef
for any kind of actors.
They themselves are not actors, but Actor
returned by their Get()
method is
guaranteed to be safe to use:
class MyObject extends AcediaObject;
var NativeActorRef pawnReference;
// ...
protected function Finalizer()
{
_.memory.Free(pawnReference); // This does not destroy stored pawn!
pawnReference = none;
}
function Pawn GetMyPawn()
{
if (pawnReference == none) {
return none;
}
return Pawn(pawnReference.Get());
}
function SetMyPawn(Pawn newPawn)
{
if (pawnReference == none)
{
// `UnrealAPI` deals with storing non-Acedia actors such as `Pawn`.
// For `AcediaActor`s you can also use `_.ref.Actor()`.
pawnReference = _.unreal.ActorRef(newPawn);
}
else {
pawnReference.Set(newPawn);
}
}
function DoWork()
{
local Pawn myPawn;
myPawn = GetMyPawn();
if (myPawn == none) {
return;
}
// <Some code that might `Destroy()` our pawn>
// ^ After destroying a pawn,
// `myPawn` local variable might go "bad" and cause crashes,
// so it's a good idea to "update" it from the safe `pawnReference`:
myPawn = GetMyPawn();
if (myPawn != none) {
myPawn.health += 10;
}
}
NOTE: Actor boxes do not exist, since we cannot guarantee that value inside them will never change - destroying stored actor will always reset it to
none
.
Array boxes and references
If necessary, box and reference classes can be manually created for any type
of value, including array<...>
s and struct
s.
Acedia provides such classes for arrays of primitive value types out of the box.
They can be useful for passing huge arrays between objects and functions
by reference, without copying their entire data every time.
They also provide several convenience methods - here is a list for
FloatArrayRef
's methods as an example:
Method | Description |
---|---|
Get() | Returns the whole stored array as array<float> . |
Set(array<float>) | Sets the whole array value. |
GetItem(int, optional float) | Returns item at specified index. If index is invalid, returns passed default value. |
SetItem(int, float) | Changes array's value at specified index. |
GetLength() | Returns length of the array. ref.GetLength() is faster than ref.Get().length , since latter will make a copy of the whole array first |
SetLength(int) | Resizes stored array, doing nothing on negative input. |
Empty() | Empties stored array. |
Add(int) | Increases length of the array by adding specified amount of new elements at the end. |
Insert(int index, int count) | Inserts count zeroes into the array at specified position. The indices of the following elements are increased by count in order to make room for the new elements. |
Remove(int index, int count) | Removes number elements from the array, starting at index . All elements before position and from index + count on are not changed, but the element indices change, - they shift to close the gap, created by removed elements. |
RemoveIndex(int) | Removes value at a given index, shifting all the elements that come after one place backwards. |
AddItem(float) | Adds given float at the end of the array, expanding it by 1 element. |
InsertItem(int, float) | Inserts given item at index of the array, shifting all the elements starting from index one position to the right. |
AddArray(array<float>) / AddArrayRef(FloatArrayRef) | Adds given array of items at the end of the array, expanding it by inserted amount. |
InsertArray(array<float>) / InsertArrayRef(FloatArrayRef) | Inserts items array at specified index of the array, shifting all the elements starting from index by inserted amount to the right. |
RemoveItem(float, bool) | Returns all occurrences of item in the caller float (optionally only first one). |
Find(float) | Finds first occurrence of specified item in caller FloatArrayRef and returns its index. |
Replace(float search, float replacement) | Replaces any occurrence of search with replacement . |
Sort(optional bool descending) | Sorts array in either ascending or descending order. |