Jump to content
Sign in to follow this  
igneous01

256 bit integers and doubles? Also: Create a reference to a variable?

Recommended Posts

I haven't noticed this until now - but the maximum and minimum limits of a variable is (2 ^ 128) -1 ( (3.4028236692093846346337460743177 e+38) - 1 and (-3.4028236692093846346337460743177 e+38) - 1), this seems to imply that a variable storing numbers is actually 256 bits in length (or 32 bytes) for both integers and doubles.

Are all variables that store numbers default allocated 32 bytes? If this is the case, then having 100 variables storing numbers would take up 3200 bytes (around 3.2 kb of space). That might not seem like a lot, but doing any sort of manipulation of such huge datatypes would be extremely slow.

Just to give perspective, in c++ integer is usually 4 bytes (32 bits) with double being 8 bytes, of course there are 64 bit variants, and 128 (that is only supported on a few architectures). I'm wondering how the team managed to make 256 bit signed integers and doubles work in the game. Has any script in sqf actually needed to use numbers this ridiculously big?

Second question: Is there a way I can create a variable that is a reference to another variable that is stored via setVariable? Im working on a cheap 'adapter' for Object Oriented SQF, and I want to be able to have an instance of a class reference a static member from the class. Ie.

//Player_T class (contains a static field "myStaticField") - 
Player_T setVariable["myStaticField", 1];

//create 2 player instances, player_1 and player_2  - 
player_1 setVariable["myStaticField", (Player_T getVariable "myStaticField")]; // reference the class variable

//If I change myStaticField in either player_1, player_2, or even Player_T, that change is reflected through all of them.
//ie. player_1 setVariable["myStaticField", 2]; would look like so
// Player_T::myStaticField = 2
// player_1.myStaticField = 2
// player_2.myStaticField = 2

however when I tried testing this, it didn't work, each object had its own copy of the static member, and thats not what I want. I know that SQF doesn't really support references, but they are used when assigning to an array, but I'm hoping there is another way to have it reference the static field. Any ideas?

Share this post


Link to post
Share on other sites

This is something up my alley, as I've spent waaaay more time than is healthy screwing around with creating pseudo-datatypes within SQF. You seem to know your stuff pretty well, so a lot of what I'm saying will probably be old news to you, but I figure a fairly thorough explanation of what I'm doing may be of value to others. I've toyed around with trying to create a proper object-oriented substitute before, but never really followed through beyond making what was effectively an associative array.

Hokay, so to answer the first part of the question, there are only two datatypes in SQF that are passed by reference: Objects and Arrays (As far as I'm aware). Every other value, even those gotten from an object/array, is passed by value. So my solution is to make each class itself an object, then have each instance of it contain a reference, and a few other specified values. To give you an idea, here's what I whipped up:

/***********************************************************************************
Super_Class's Init
***********************************************************************************/

Super_Class = "Logic" createVehicleLocal [0,0,0];
//Declare & Initialize all static fields here
Super_Class setVariable ["extraDescription", "See, it works up multiple levels too!"];

//Now let's create a pseudo-constructor, to initialize an instance of this pseudo-class
Super_Class setVariable ["constructor", {
   private["_this","_object","_params"];
   _object = _this select 0;
   _params = _this select 1;
   _object setVariable ["extends", Super_Class];

   //Declare & Initialize any public variables here
}];

/***********************************************************************************
Test_Class's Init
***********************************************************************************/

Test_Class = "Logic" createVehicleLocal [0,0,0];
//Declare & Initialize all static fields here
Test_Class setVariable ["description", "Just a Test Class!"];
Test_Class setVariable ["extends", Super_Class];

//Now let's create a pseudo-constructor, to initialize an instance of this pseudo-class
Test_Class setVariable ["constructor", {
   private["_this","_object","_params"];
   _object = _this select 0;
   _params = _this select 1;
   _object setVariable ["extends", Test_Class];

   //Declare & Initialize any public variables or methods here
   _object setVariable ["color", "Blue"];
   _object setVariable ["number", 0];
   _object setVariable ["foo", "bar"];
   _object setVariable ["incrementNum", {
       _object setVariable ["number", (_object getVariable "number") + 1];
   }];
}];

/***********************************************************************************
End Class Init, Now Functions
***********************************************************************************/

/*
Behavior:
   Creates a new object of a specified class and returns its reference
Syntax:
   [_class, _params] call fnc_newObject;
Params:
   0: Classtype of new object
   1: Any additional parameters for constructor
Returns:
   Object
*/
fnc_newObject = {
   private["_this","_class","_params","_output"];
   _class  = [_this, 0, objNull, [objNull]] call BIS_fnc_param;
   _params = [_this, 1, [], [[]]] call BIS_fnc_param;
   if (isNull _class) exitWith {_class}; //Check to ensure master class is valid
   _output = "Logic" createVehicleLocal [0,0,0];
   [_class, "constructor", ([_output] + _params)] call fnc_callMethod;
   _output
};

/*
Behavior:
   Gets the value of a field within an object
Syntax:
   [_object, _field, _default] call fnc_getField;
Params:
   0: Object from which to get field
   1: Field to get from object
   2: Default value to use, otherwise nil is default(Optional)
Returns:
   Anything
*/
fnc_getField = {
   private["_this","_object","_field","_default","_super","_target"];
   _object  = _this select 0;
   _field   = _this select 1;
   _default = if (count _this >= 2) then {_this select 2};
   _super   = _object getVariable ["extends", objNull];
   _target  = _object getVariable _field;

   if ( !isNil("_target") ) exitWith {
       _target
   };
   if (isNull _super && !isNil "_default") exitWith {_default};
   if (isNull _super && isNil "_default") exitWith {};
   [_super, _field] call fnc_getField
};

/*
Behavior:
   Sets a field within an object, only if it exists
Syntax:
   [_object, _field, _value] call fnc_setField;
Params:
   0: Object whose field should be set
   1: Field to set within object
   2: Value to assign to field
Returns:
   Boolean for success state
*/
fnc_setField = {
   private["_this","_object","_field","_value","_super","_target"];
   _object = _this select 0;
   _field  = _this select 1;
   _value  = _this select 2;
   _super  = _object getVariable ["extends", objNull];
   _target = _object getVariable _field;

   if ( !isNil("_target") ) exitWith {
       _object setVariable [_field, _value];
       true
   };
   if (isNull _super) exitWith {false};
   [_super, _field, _value] call fnc_setField
};

/*
Behavior:
   Calls a method/function within a given object
Syntax:
   [_object, _method, _params] call fnc_callMethod;
Params:
   0: Object whose method will be called
   1: Method name to call
   2: Params to pass to the method
Returns:
   Anything
*/
fnc_callMethod = {
   private["_this","_object","_method","_params","_code"];
   _object  = _this select 0;
   _method  = _this select 1;
   _params  = _this select 2;
   _code    = [_object, _method, {}] call fnc_getField;
   if (typeName(_code) == typeName({})) exitWith {
       _params call _code
   };
};

//Example Code:

_obj0 = [Test_Class, []] call fnc_newObject;

[_obj0, "incrementNum", []] call fnc_callMethod;

hint format ["%1\n%2\n%3\n%4\n%5",
   [_obj0, "description"] call fnc_getField,
   [_obj0, "color"] call fnc_getField,
   [_obj0, "number"] call fnc_getField,
   [_obj0, "foo"] call fnc_getField,
   [_obj0, "extraDescription"] call fnc_getField
];

deleteVehicle _obj0;

Ok, so there's quite a bit happening in here, but it ultimately gives the functionality you're looking for. The functions' descriptions lists their behaviors, so hopefully those are fairly self explanatory, but how exactly this works may be a bit muddled. The most noteworthy thing is how I create an object for every Class itself. Any values you setVariable to this object up in it's pseudo-init area will be effectively a static variable. Each of these classes has 1-2 very important variables: "constructor", and "extends".

- Constructor is the function that is called when you call fnc_newObject, so any public/instance variables are initialized in there.

- Extends on the other hand, comes into play in the functions fnc_getField, fnc_setField and fnc_callMethod, as it contains a reference to the master class's object. When looking for a field within an object, these functions first check the object itself. If the field isn't found within the instance of the object, it steps up to check to see if the Class object has that field set. If it doesn't, it'll check to see if the class itself extends any other classes, and checks them if so. Configuring this as such has the benefit that any changes to the field within the class will be reflected immediately in all instances of it. It also opens the door to multiple levels of extension, as briefly demonstrated in the example code.

Also note that even though this uses the createVehicleLocal command, the object it creates is global to the executing client. So you need to manage your memory just a bit by deleteVehicle-ing it when you're done, or else you may eventually cause issues. Also worth noting that I'm not sure if createVehicleLocal on a "Logic" is the best practice for this, but it's what I know how to do so I've been using it. Any change is negligible to the rest of the code.

This is my first real attempt at doing something quite like this, so there may be a few quirks to work out. I don't even 100% remember all of the important rules and characteristics of OOP, so I may need to refresh myself before looking at this more. Regardless, this should hopefully give you an idea of what you may be able to accomplish with SQF's fairly clunky objects. If there's an interest (and heck probably even if there isn't) I'll probably clean up and finish off these scripts, as I could definitely see uses where they may come in handy. Also, I know you asked for just a bit of advise on SQF's calling by reference, but I got a bit excited and made my example a bit more in depth than initially planned. :P If you have any other questions about this, I'll be glad to answer!

Edited by Hypnomatic

Share this post


Link to post
Share on other sites

Thats a nice example, bit of recursion to drill down into the inheritance via extends. I like.

Share this post


Link to post
Share on other sites
This is something up my alley, as I've spent waaaay more time than is healthy screwing around with creating pseudo-datatypes within SQF. You seem to know your stuff pretty well, so a lot of what I'm saying will probably be old news to you, but I figure a fairly thorough explanation of what I'm doing may be of value to others. I've toyed around with trying to create a proper object-oriented substitute before, but never really followed through beyond making what was effectively an associative array.

Hokay, so to answer the first part of the question, there are only two datatypes in SQF that are passed by reference: Objects and Arrays (As far as I'm aware). Every other value, even those gotten from an object/array, is passed by value. So my solution is to make each class itself an object, then have each instance of it contain a reference, and a few other specified values. To give you an idea, here's what I whipped up:

Ok, so there's quite a bit happening in here, but it ultimately gives the functionality you're looking for. The functions' descriptions lists their behaviors, so hopefully those are fairly self explanatory, but how exactly this works may be a bit muddled. The most noteworthy thing is how I create an object for every Class itself. Any values you setVariable to this object up in it's pseudo-init area will be effectively a static variable. Each of these classes has 1-2 very important variables: "constructor", and "extends".

- Constructor is the function that is called when you call fnc_newObject, so any public/instance variables are initialized in there.

- Extends on the other hand, comes into play in the functions fnc_getField, fnc_setField and fnc_callMethod, as it contains a reference to the master class's object. When looking for a field within an object, these functions first check the object itself. If the field isn't found within the instance of the object, it steps up to check to see if the Class object has that field set. If it doesn't, it'll check to see if the class itself extends any other classes, and checks them if so. Configuring this as such has the benefit that any changes to the field within the class will be reflected immediately in all instances of it. It also opens the door to multiple levels of extension, as briefly demonstrated in the example code.

Also note that even though this uses the createVehicleLocal command, the object it creates is global to the executing client. So you need to manage your memory just a bit by deleteVehicle-ing it when you're done, or else you may eventually cause issues. Also worth noting that I'm not sure if createVehicleLocal on a "Logic" is the best practice for this, but it's what I know how to do so I've been using it. Any change is negligible to the rest of the code.

This is my first real attempt at doing something quite like this, so there may be a few quirks to work out. I don't even 100% remember all of the important rules and characteristics of OOP, so I may need to refresh myself before looking at this more. Regardless, this should hopefully give you an idea of what you may be able to accomplish with SQF's fairly clunky objects. If there's an interest (and heck probably even if there isn't) I'll probably clean up and finish off these scripts, as I could definitely see uses where they may come in handy. Also, I know you asked for just a bit of advise on SQF's calling by reference, but I got a bit excited and made my example a bit more in depth than initially planned. :P If you have any other questions about this, I'll be glad to answer!

very impressive attempt here! I am currently doing something very similar, except replacing gamelogics with invisible helipads to reduce workload involving groups of gamelogics. So far, I have managed to get encapsulation working - fields, methods, and constructor, still need to add a destructor that would simply call BIS garbage collect to delete the object after (too bad RAII is not possible, but I guess that means treat your instances like heap pointers).

I will probably be spending a few weeks making sure inheritance works as it should.

I see that you are using a function that wraps getVariable to return a field which makes accessing static data from the class quite easy, I think I will also do that (Im currently using macros) I've set up a unit test and so far class designs look like this for me:

// Example Player class for unit testing mechanics

_ctor = 
{
_params = _this select 1;
//hint format ["Defined CTOR called: %1, %2", _me, _params];
_me DOT_S("_player", _params);
_me DOT_S("_uid", (getPlayerUID _params));
_me DOT_S("_side", (side _params));
_me DOT_S("_curWep", (currentWeapon _params));
};

// parameters: weapon "string"
// changes the player's current weapon
_changeCurWeapon = 
{
// first remove old current weapon
hint "called changeCurrentWeapon from somePlayer";
(_me DOT_G("_player")) removeWeapon (_me DOT_G("_curWep"));
(_me DOT_G("_player")) addWeapon _args;
_me DOT_S("_curWep", _args);

// Desc: select default weapon & handle multiple muzzles
if (count weapons (_me DOT_G("_player")) > 0) then
{
  private['_type', '_muzzles'];

  _type = ((weapons (_me DOT_G("_player"))) select 0);
  // check for multiple muzzles (eg: GL)
  _muzzles = getArray(configFile >> "cfgWeapons" >> _type >> "muzzles");

  if (count _muzzles > 1) then
  {
	(_me DOT_G("_player")) selectWeapon (_muzzles select 0);
  }
  else
  {
	(_me DOT_G("_player")) selectWeapon _type;
  };
};
};

// The actual class design itself, _class is a macro that wraps the call to DECLARE_CLASS
_class(Player_T, 
[
	"_player" _comma
	"_uid" _comma
	"_side" _comma
	"_curWep"
],
_ctor,
[
	["changeCurrentWeapon" _comma _changeCurWeapon]
]
);


// Unit test
// The real test
BOOST_TEST_BEGIN(CLASS_REAL_TEST)

somePlayer = _construct(Player_T, player);

// this passed
//_exp = format ["%1 == %2", str (somePlayer DOT_G("_player")), str player];
//BOOST_CHECK(_exp);

_exp = format ["%1 == %2", (somePlayer DOT_G("_side")), WEST];
BOOST_CHECK(_exp);

_exp = format ["!(%1)", (isNil{Player_T DOT_G("changeCurrentWeapon")})];
BOOST_CHECK(_exp);

// call changeCurrentWeapon
_call(somePlayer,changeCurrentWeapon, "arifle_Mk20_F");		// "m16a4" old classname in A2 for testing

_exp = format ["%1 == %2", str (somePlayer DOT_G("_curWep")), str "arifle_Mk20_F"];
BOOST_CHECK(_exp); 

hint format ["somePlayer: uid = %1, curWep = %2, side = %3", somePlayer DOT_G("_uid"), somePlayer DOT_G("_curWep"), somePlayer DOT_G("_side")];

RELEASE(somePlayer);
RELEASE(Player_T);

BOOST_TEST_END(CLASS_REAL_TEST)

It is definitely not pretty looking (notice _comma macro to allow nested , ) but at least the design aspect resembles something similar to other languages. Would you think using macros, or wrapping functions would be better for overall readability?

Share this post


Link to post
Share on other sites
I haven't noticed this until now - but the maximum and minimum limits of a variable is (2 ^ 128) -1 ( (3.4028236692093846346337460743177 e+38) - 1 and (-3.4028236692093846346337460743177 e+38) - 1), this seems to imply that a variable storing numbers is actually 256 bits in length (or 32 bytes) for both integers and doubles.

No it does not imply that.

You can store the number (2 ^ 128)-1 in an unsigned integer of 128 bit. However, floating point numbers have a different internal representation that works differently. You can represent (2 ^ 128)-1 (and no more!) using only single-precision float (4 bytes), however, you are not guaranteed the less significant numbers are accurate. Likewise you can store 0.000000000053143453132231 in a floating point number, again no guarantee that it is exactly the number you guaranteed. Basically a 'normal' int and float each only take 4 bytes, giving 32 bits, giving 2^32 different possibilities. Integers can represent, well integers, accurately as long as they are withing the range +(2^31 - 1) to -(2^31) for signed integers. Floats can represent much larger or smaller numbers, but can only accurately represent least significant numbers. A 4 byte float should give you between 6 to 9 significant (decimal) digits.

My guess is that the engine either:

- Stores all numbers as C floats. This causes no issue normally since most numbers are are less than 9 digits long.

- Converts between C floats and C integers automatically.

Share this post


Link to post
Share on other sites
I haven't noticed this until now - but the maximum and minimum limits of a variable is (2 ^ 128) -1 ( (3.4028236692093846346337460743177 e+38) - 1 and (-3.4028236692093846346337460743177 e+38) - 1), this seems to imply that a variable storing numbers is actually 256 bits in length (or 32 bytes) for both integers and doubles.

Are all variables that store numbers default allocated 32 bytes? If this is the case, then having 100 variables storing numbers would take up 3200 bytes (around 3.2 kb of space). That might not seem like a lot, but doing any sort of manipulation of such huge datatypes would be extremely slow.

Just to give perspective, in c++ integer is usually 4 bytes (32 bits) with double being 8 bytes, of course there are 64 bit variants, and 128 (that is only supported on a few architectures). I'm wondering how the team managed to make 256 bit signed integers and doubles work in the game. Has any script in sqf actually needed to use numbers this ridiculously big?

...

My understanding is that in Arma scripts all numbers are standard IEEE 754 single precision 32 bit floating point numbers. Proof:

max number = (2^127) * (2-2^(-23)) == 3.40282e+038

max discrete (x != x+1) integer = 2^24 == 16777216

smallest normal number = 2^(-126) * 1 == 1.17549e-038

smallest denormal number = 2^(-127) * (2^(-22)) == 1.4013e-45

Reference: http://en.wikipedia.org/wiki/IEEE_754-1985#Single_precision

Edited by ceeeb
corrected denormal number calc

Share this post


Link to post
Share on other sites
My understanding is that in Arma scripts all numbers are standard IEEE 754 single precision 32 bit floating point numbers. Proof:

max number = (2^127) * (2-2^(-23)) == 3.40282e+038

max discrete (x != x+1) integer = 2^24 == 16777216

smallest normal number = 2^(-126) * 1 == 1.17549e-038

smallest denormal number = 2^(-127) * (2^(-22)) == 1.4013e-45

Reference: http://en.wikipedia.org/wiki/IEEE_754-1985#Single_precision

This is the case. You can clearly see this if you examine the assembly code for SQF math functions in the engine, everything is 4 byte offsets for numbers in their variable storage system, even if they are using SSE/MMX instruction sets (which there is a lot of in SQF) the results are all 32bit floats.

Share this post


Link to post
Share on other sites

now I understand, for some reason when I was trying to search for 4 byte values in memory (Aka cheat engine, to see how big script stack space is) I could not find my variables, I was under the assumption that the sizes had to be larger, but I guess I will try to search 4 byte floats instead.

And no, im not trying to hack the game for malicious intent, I was thinking of making a basic external debugger that would read a scripts stack space (since you cant watch local vars using the console in game)

Thanks guys for pointing out my error, now I know what to look for.

Share this post


Link to post
Share on other sites

Good luck with that... I have wracked my brain over and over, so did Jaynus on trying to interact with the SQF engine in any coherent way. It is not easy. The hoops that had to be jumped through just to intercept values from a single SQF function in the old Jlib implementation is really... not fun.

Seriously though, good luck, if you figure anything key out make sure to post it public so others can benefit from it!

Share this post


Link to post
Share on other sites

A possibly more realistic idea of a debugger is writing some kind of preprocessor script that converts your entire mission to a debug-able mission, using macros to set breakpoints. Due to parsing you'll be able to tell all the possible values that could theoretically be watched, except of course for variable names created from strings generated during runtime (aka "variable variable names", usually very bad practice anwyay). For "release" builds the mission will be practically the same as the macros can be defined to nothing. Of course you'd still have to make proper in-game UI for it and even if you do perfect job it will likely not be easy to use.

Trying to monitor memory during run-time will probably result in the values changing faster than you can monitor them :(

Share this post


Link to post
Share on other sites
A possibly more realistic idea of a debugger is writing some kind of preprocessor script that converts your entire mission to a debug-able mission, using macros to set breakpoints. Due to parsing you'll be able to tell all the possible values that could theoretically be watched, except of course for variable names created from strings generated during runtime (aka "variable variable names", usually very bad practice anwyay). For "release" builds the mission will be practically the same as the macros can be defined to nothing. Of course you'd still have to make proper in-game UI for it and even if you do perfect job it will likely not be easy to use.

Trying to monitor memory during run-time will probably result in the values changing faster than you can monitor them :(

My language, which is in a state of rest at the moment, offers all of that, interactive debugging, stack traces, variable traces, etc.

Problem is the performance goes down the tubes the more feature you want... Hmm... Mainly though because I hate the SQF scheduler and want to write my own, which requires significant overhead in how the compiled code is executed in SQF.

Share this post


Link to post
Share on other sites

Yeah the idea was that you can write normal sqf code, run an extrenal tool, and get a debug version of the mission, and maybe only include debug features on specific scripts. That way when you actually play the mission none of that overhead will run.

Wrapping an entire language though will always have extreme overhead... Writing a scripting language within a scripting language is just too crazy to get any reasonable performance out of it :(

Share this post


Link to post
Share on other sites
Yeah the idea was that you can write normal sqf code, run an extrenal tool, and get a debug version of the mission, and maybe only include debug features on specific scripts. That way when you actually play the mission none of that overhead will run.

Wrapping an entire language though will always have extreme overhead... Writing a scripting language within a scripting language is just too crazy to get any reasonable performance out of it :(

The way I've written it though is nice because if there is increased interaction with the engine in the future it'd be pretty easily to implement at a close to native level (just need to change the instruction command output/parser).

Share this post


Link to post
Share on other sites

I think a lot of this would probably be extremely easy to implement if there was a "modified" event handler that would retrieve the file, line and variable that was changed. Then you can log any changes and figure out what happened where.

@galzohar

A preprocessor is probably the best solution right now, but I dont like the idea of writing another parser overtop of sqf parser :(

Maybe callExtension? Does anyone know if the arguments passed are copied, or marshalled?

Share this post


Link to post
Share on other sites

I really love how anything advance requires hacky variables set on logics or markers. BI really needs to give use the ability to create objects via script....

que dead horse....

the way VBS does, at least in vbs you can create classes via script.

Share this post


Link to post
Share on other sites

Honestly giving most people that do SQF OOP would just confuse them and make for worse performing code. :(

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
Sign in to follow this  

×