-
Notifications
You must be signed in to change notification settings - Fork 44
[Scripting] DayZ EnforceScript Pitfalls
This page was born out of my frustrations with DayZ EnforceScript and has nothing to do with the Expansion Mod.
It is a work in progress and by no means complete.
Class c
is not a class, but an instance
typename t
is a class
<instance>.Type()
returns a typename
(class)
<some_object>.GetType()
returns the config.cpp entry class name for the object as string (not necessarily the same as <instance>.ClassName()
, and GetType()
should be preferred over it in most cases)
<instance>.Type().ToString()
is the same as <instance>.ClassName()
, so use the latter
Everything (except Managed) is a weak reference in Enforce by default. The garbage collector is very aggressive (e.g. instances in function scope are destroyed immediately when scope ends).
Documentation on Managed
vs 'unmanaged' class and delete
keyword is misleading or plain wrong in parts (https://community.bistudio.com/wiki/DayZ:Enforce_Script_Syntax, the section on automatic reference counting, the part on ref
keyword is correct though)
TLDR:
ref
(or autoptr
- don't use both!) is needed for any complex non-Managed class member that should not be garbage collected until the containing instance is destroyed (and ONLY there, do not use ref
anywhere else! E.g. not for functions or function parameters, or inside functions).
(See this post by MarioE on the Enfusion Modders Discord for more details)
There is no need to use the delete
keyword, ever. All instances, Managed or not, will be garbage collected when the reference count reaches zero (you can add prints to destructors to see this in action). Using delete
on an object that is still used can segfault.
Correct use of ref
:
class MyClass
{
ref map<string, ref TStringArray> m_Example = new map<string, ref TStringArray>;
map<string, ref TStringArray> Get()
{
return m_Example;
}
}
Wrong use of ref
:
class MyClass
{
ref TIntArray Example(ref TIntArray oh_god) //! Bad refs in method declaration
{
ref TIntArray myArray = new ref TIntArray(); //! Bad ref in variable declaration and instantiation
return myArray;
}
}
Exception logs are named crash_<date>_<time>.log
, despite not having anything to do with actual crashes (segfaults, see further below).
Compile error messages involving either a class that was never defined (e.g. because a required addon is not loaded) or a variable name that conflicts, never include the correct filename and line number, the filename and line number shown in the error is of the last successfully parsed .c
file at EOF, which is misleading and not helpful at all.
GetGame().IsClient()
returns false on client during load, use !GetGame().IsDedicatedServer()
instead.
GetGame().IsServer()
returns true on client during load, use GetGame().IsDedicatedServer()
instead (unless you want to support offline/singleplayer mode as well).
foreach
cannot be used directly on iterables returned by getters, you have to assign to a variable and iterate over that.
class MyClass
{
autoptr TStringArray m_Test = {"A", "B"};
TSTringArray GetTest()
{
return m_Test;
}
void Test()
{
foreach (string t: GetTest())
{
// will throw NULL pointer exception on 2nd item
}
}
void Test2()
{
foreach (string t: m_Test)
{
// fine
}
}
void Test3()
{
TStringArray test = GetTest();
foreach (string t: test)
{
// fine
}
}
}
// int.MIN = -2147483648
1 < int.MIN; //! Yes, this is true in EnForce
1 < -2147483647; //! This as well
//! array type not important, in this case array<int>
if (!list[1]) //! doesn't compile
if (list[1] == 0) //! ok
//! even though this function is guaranteed to always return a value due to default case,
//! Enforce complains that it needs to return a value. Workaround is to not have default case and move
//! last return statement out of switch
string Test(string what)
{
switch (what)
{
case "A":
case "B":
return "X";
default:
return "Y";
}
}
Segfault on numerous language constructs that would be perfectly fine in any other programming language.
Example:
class MyClass
{
bool m_IsInside[3];
void Test(int index, vector a, vector b, float distSq)
{
m_IsInside[index] = vector.DistanceSq(a, b) <= distSq; // segfault
}
void Test(int index, vector a, vector b, float distSq)
{
bool isInside = vector.DistanceSq(a, b) <= distSq;
m_IsInside[index] = isInside; // fine
}
}
Empty #ifdef/#ifndef blocks. This is also true if the only contents are comments.
#ifdef FOO
// segfault
#endif
Bracketing is needed for conditions involving binary operations, as it follows C/C++ operator precedence (comparison before bitwise). This can get you if you're used to Python (like me) where bitwise come before comparison operators.
int a = 1;
int b = 2;
a & b == b; // EnforceScript will see this as a & (b == b)
(a & b) == b;
Copyright© 2020-2023 DayZ Expansion Mod Team. We do not authorize any entity to publish this DayZ Standalone modification without licensing from the DayZ Expansion Mod Team.