Jump to content
naught

Object Oriented SQF Scripting and Compiling

Recommended Posts

Object Oriented SQF Scripting

v1.3.1 - v1 Commercial Distribution b1

By Naught


GitHub: https://github.com/dylanplecki/RBS/blob/master/addons/rbs_main/h/oop.h

Issues: https://github.com/dylanplecki/RBS/issues

Download: https://raw.github.com/dylanplecki/RBS/master/addons/rbs_main/h/oop.h


Description

What is object oriented programming?

From Wikipedia, the page being a recommended read for anyone interested in the possibilities of object-oriented programming (OOP),

Rather than structure programs as code and data an object-oriented system integrates the two using the concept of an "object". An object has state (data) and behavior (code). Objects correspond to things found in the real world. So for example, a graphics program will have objects such as circle, square, menu. An online shopping system will have objects such as shopping cart, customer, product,. The shopping system will support behaviors such as place order, make payment, and offer discount. The objects are designed as class hierarchies. So for example with the shopping system there might be high level classes such as electronics product, kitchen product, and book. There may be further refinements for example under electronic products: CD Player, DVD player, etc. These classes and subclasses correspond to sets and subsets in mathematical logic.

The goals of object-oriented programming are:

  • Increased understanding.
  • Ease of maintenance.
  • Ease of evolution.

The overall understanding of the system is increased because the semantic gap -- the distance between the language spoken by developers and that spoken by users -- is lessened. Rather than talking about database tables and programming subroutines the developer talks about things the user is familiar with: objects from their application domain.

So, as you can see, Arma is already largely object-oriented with its extensive use of vehicles, units, players, shapes, groups, locations, and so on. So wouldn't it be logical to fully carry this syntax over to the SQF scripting language?

So what can I do with it?

Instead of having long, drawn-out lists of functions all centered around the same goal, we can instead have one object with many functions (behaviors) and variables (states) integrated within it. This practice can simplify complex tasks by utilizing our minds natural thinking pattern of internal and external interaction - or what "I" do, and what "they" see.

For example, in Arma we can have an object such as a "platoon", and that platoon is made up of Arma groups. Instead of making an array holding a list of all of these groups, and then having functions for adding/removing groups from the platoon, we can just have a platoon object, with an array of groups as one of its states and have the adding/removing of groups as some of the platoon's behaviors. A pseudo-sketch of this object would look like this:

class Platoon {
   array Groups;
   function AddGroup(group);
   function RemoveGroup(group);
};

So now all the programmer has to do is create a new Platoon and add/remove groups directly to the object - easy, right?


Features

  • Human-readable syntax that closely resembles PHP's OOP integration.
  • Extensively documented source-code for ease-of-use.
  • Only one header file, and it's fully macro-based, and only executed by the preprocessor.
  • Implements full class-based object inheritance.
  • Compiles to full-speed SQF code, so no need for run-time interpretation!
  • Allows for public, protected, and private class members, for instituting complex objects and inheritance.
  • Full support for polymorphism (to the limits of the SQF syntax).
  • Small memory footprint - all functions are static, and no code is unnecessarily duplicated.
  • Support for constructors and deconstructors.
  • Completely recursive via the MEMBER macro.
  • Allows for static variables, which are persistent to all classes of the same type.
  • Note: Objects cannot be copied to other machines just by sending the object variable - all data members (object variables) need to be serialized (preferably to an array), transferred, and then added to a new object on the remote machine.


Examples

Sorry for the weird indentation, that's what I get for working in Linux.

Basic Example (probably doesn't work):

#include "oop.h"

CLASS("StatSheet") // Player Statistics Sheet Object

       PRIVATE STATIC_VARIABLE("scalar","playerCount"); // Static variables are shared amongst all instances of this class
PRIVATE VARIABLE("object","player");
PRIVATE VARIABLE("scalar","kills"); // Scalar == Number
PRIVATE VARIABLE("scalar","deaths");
PRIVATE VARIABLE("scalar","score");

PUBLIC FUNCTION("object","constructor") { // Accepts an object as a parameter
	private ["_player"];
	_player = _this;
               INC_VAR("playerCount"); // Increases the member by 1
	MEMBER("player",_player); // Sets the member to the given value
	MEMBER("kills",0); // Important to initialize all necessary members
	MEMBER("deaths",0);
	MEMBER("score",0);
       };

PUBLIC FUNCTION("nil","deconstructor") { // Accepts no parameters (nil)
               DEC_VAR("playerCount"); // Decreases the member by 1
	DELETE_VARIABLE("player"); // Must delete all members on deconstruction to avoid memory leaks
	DELETE_VARIABLE("kills");
	DELETE_VARIABLE("deaths");
	DELETE_VARIABLE("score");
       };

PUBLIC FUNCTION("","addKill") {
	INC_VAR("kills");
	MEMBER("kills",nil); // Return total kills
};

PUBLIC FUNCTION("","addDeath") { // Also accepts no parameters (same as "nil", or "" == "nil")
	INC_VAR("deaths");
	MEMBER("deaths",nil); // Return total deaths
};

PUBLIC FUNCTION ("scalar","addScore") { // Accepts scalar (number) as parameter
	MOD_VAR("score",_this); // Adds the param to the member, ie. (0 + 1)
};

PUBLIC FUNCTION("","getKills") FUNC_GETVAR("kills"); // Shorthand for getting variables
PUBLIC FUNCTION("","getDeaths") FUNC_GETVAR("deaths");
PUBLIC FUNCTION("","getScore") FUNC_GETVAR("score");

ENDCLASS;

EXAMPLE_fnc_addStatTracker = {
private ["_unit", "_stats"];
_unit = _this select 0;
_stats = ["new", _unit] call StatSheet;
["addScore", (score _unit)] call _stats;
_unit setVariable ["stats", _stats];
};

EXAMPLE_fnc_remStatTracker = {
private ["_unit", "_stats"];
_unit = _this select 0;
_stats = _unit getVariable ["stats", {}]; // All objects are actually a "code" type
_unit setVariable ["stats", nil];
["delete", _stats] call StatSheet;
};

Advanced Example:

/*
Title: Hashmap Library
Author: Dylan Plecki (Naught)
Version: 1.0.3.1 - v1.0 CD b1

API Example:
	_hm = ["new"] call UCD_obj_hashMap;
	["insert", ["key", value]] call _hm;
	_val = ["get", ["key", defaultValue]] call _hm;
	["erase", "key"] call _hm;
	["delete", _hm] call UCD_obj_hashMap;

License:
	Copyright © 2013 Dylan Plecki. All rights reserved.
	Except where otherwise noted, this work is licensed under CC BY 4.0,
	available for reference at <http://creativecommons.org/licenses/by/4.0/>.
*/

#include "oop.h" // Required

/*
Group: Hashmap Object
*/

CLASS("UCD_obj_hashMap") // Unique unordered map

PRIVATE VARIABLE("array", "map");

PUBLIC FUNCTION("","constructor") {
	#define DFT_MAP [[],[]] // Since param macros can't correctly parse comma-delimited arrays
	MEMBER("map",DFT_MAP);
};

PUBLIC FUNCTION("","deconstructor") {
	DELETE_VARIABLE("map");
};

PUBLIC FUNCTION("array","get") { // ["key", default], returns value (any)
	private ["_map", "_index"];
	_map = MEMBER("map",nil);
	_index = (_map select 0) find (_this select 0);
	if (_index >= 0) then {
		(_map select 1) select _index;
	} else {_this select 1};
};

PUBLIC FUNCTION("string","get") { // "key", returns value (any)
	private ["_args"];
	_args = [_this, nil];
	MEMBER("get",_args);
};

PUBLIC FUNCTION("array","insert") { // ["key", value], returns overwritten (bool)
	private ["_map", "_index"];
	_map = MEMBER("map",nil);
	_index = (_map select 0) find (_this select 0);
	if (_index >= 0) then {
		(_map select 1) set [_index, (_this select 1)];
		true;
	} else {
		_index = count (_map select 0);
		(_map select 0) set [_index, (_this select 0)];
		(_map select 1) set [_index, (_this select 1)];
		false;
	};
};

PUBLIC FUNCTION("string","erase") { // "key", returns success (bool)
	private ["_map", "_index"];
	_map = MEMBER("map",nil);
	_index = (_map select 0) find (_this select 0);
	if (_index >= 0) then {
		private ["_last"];
		_last = (count (_map select 0)) - 1;
		(_map select 0) set [_index, ((_map select 0) select _last)];
		(_map select 1) set [_index, ((_map select 1) select _last)];
		(_map select 0) resize _last;
		(_map select 1) resize _last;
		true;
	} else {
		false;
	};
};

PUBLIC FUNCTION("","copy") { // nil, returns hashmap (array)
	MEMBER("map",nil);
};

PUBLIC FUNCTION("array","copy") { // [hashMap], returns nothing (nil)
	MEMBER("map",_this);
};

ENDCLASS;


Success Stories

Edited by Naught
Minor updates

Share this post


Link to post
Share on other sites

Very cool, I've been itching to see something to this effect get fully implemented! Just to give you a few places to look for possible further inspiration, NouberNou had been working on a something up this alley a bit ago, but I believe he's not actively worked on it for a while:

http://forums.bistudio.com/showthread.php?134053-Carma-A-C-to-SQF-quot-native-quot-Interface

And then for a bit of self-plugging, I threw together a very quick and crude concept for some manner of OOP-alike a bit ago in response to a thread:

http://forums.bistudio.com/showthread.php?166558-256-bit-integers-and-doubles-Also-Create-a-reference-to-a-variable&p=2530812&viewfull=1#post2530812

It's not pretty, and I may have made a slipup in terminology or general programming concepts along the way, but it takes a bit of a different approach to things from your solution, so maybe it'll be interesting or possibly helpful. You seem to have all of your bases covered in that regard however, so I'm not too worried.

Either way, looking forward to seeing what comes of this if you take it all the way!

Share this post


Link to post
Share on other sites

Nice work on that - would be interested to hear more on the subject - again nice work

Share this post


Link to post
Share on other sites

That's some pretty neat sounding stuff Naught. A bit over my head, but I enjoyed reading all of it in any case.

Share this post


Link to post
Share on other sites

I'm interested in this OOP scripting. I realize it's supposed to be a "low maintenance", "easy to understand" and "easy to evolve" scripting method. But for someone like me it's the exact opposite simply because it looks a bit "foreign" to what I'm accustomed to. I tried the basic example and got errors and CTD. There's an include, a class type and functions at the very bottom. Not sure where each goes. Assuming the functions go into an .sqf file. I did assume the rest went into an .h file (save for the actual include).

Would it be possible for you to provide a basic example mission along side the basic example? This would help the folks who may not have experience in writing code outside of .sqf. Thanks.

Kind Regards,

David

Share this post


Link to post
Share on other sites

just a flag to follow your work :D

i m a bit disappointed by the call method schem:

[function, parameter] call object

is there a way to reverse it ?

object call [function, parameter]

Edited by code34

Share this post


Link to post
Share on other sites
Iceman77 and anyone else who may find this oop SQF interesting - useful website for learning various languages... however look at the PHPobject orientated part - may help shed some light on it.

http://www.codecademy.com/tracks/php

Nice. I was mainly interested explicitly in what Naught is doing here.

Share this post


Link to post
Share on other sites
I'm interested in this OOP scripting. I realize it's supposed to be a "low maintenance", "easy to understand" and "easy to evolve" scripting method. But for someone like me it's the exact opposite simply because it looks a bit "foreign" to what I'm accustomed to. I tried the basic example and got errors and CTD. There's an include, a class type and functions at the very bottom. Not sure where each goes. Assuming the functions go into an .sqf file. I did assume the rest went into an .h file (save for the actual include).

Would it be possible for you to provide a basic example mission along side the basic example? This would help the folks who may not have experience in writing code outside of .sqf. Thanks.

Kind Regards,

David

I won't lie, the basic example is something I coded up in like a minute and I'm pretty sure it was never going to work, it's just used to explain the syntax.

Anyways, here's an example on how to implement the hashmap object:

https://dl.dropboxusercontent.com/u/25977070/Games/Arma%202/Scripts/Hashmaps.rar

just a flag to follow your work :D

i m a bit disappointed by the call method schem:

[function, parameter] call object

is there a way to reverse it ?

object call [function, parameter]

I tried as hard as I could to find an appropriate and simple SQF syntax for calling objects, but this is the best you can do within the confines of SQF. Believe me I'd love a more 'natural' syntax.

Parameters have to lead the code in a call statement, and this cannot be fixed without a macro of some kind, and for simplicity's sake I wanted to only require the OOP header for where objects are defined.

---------- Post added at 12:20 ---------- Previous post was at 12:18 ----------

Nice. I was mainly interested explicitly in what Naught is doing here.

He posted that meaning that PHP's implementation is close enough to mine that you can learn one and use it to learn the other - it's the same principles and basics.

Share this post


Link to post
Share on other sites
i m a bit disappointed by the call method schem:

[function, parameter] call object

is there a way to reverse it ?

object call [function, parameter]

thats the hard-wired way arma sqf does it, i doubt you'll find a simple or logical way to swap it

@Naught - nice work! Itching to find time to play around with it...

Share this post


Link to post
Share on other sites

Just downloaded the example that is some nice work, but I can kinda see what Johnson11B2P is saying though, and i get where Iceman77 is coming from.

I think they (community) would appreciate it if you could provide a simple missions, with a simple explanation to assist (I dont mean the actual OOP principles, but how to implement your classes once created).

I actually think that if this could be utilised correctly (and with enough support), this would definitely improve the scripting levels in Arma. And it may assist those wanting to learn SQF by following website like i posted, which then offers a greater volume of tutorials etc. And more importantly a greater understanding of coding.

I've not programmed in PHP before, but i think ill take a look just to see where this leads. However i can see its fairly straight forward if you have OOP understanding and experience.

All in all - Nice work Keep it up

Share this post


Link to post
Share on other sites
Who is your main target for creating this OOD? I believe this is way over most people's head.

Scripters who come from a coding background other than SQF. Most (and I really mean MOST) other languages strongly focus on object-oriented design, and the programmers who coded in these languages for years are very well-versed in OOP. It helps speed-up design, implementation, and execution, and is really a great tool for people who have learned it.

But if you're just scripting in SQF and not any other language, my guess will be that you won't find much use for it unless you want to learn how to code in OOP-standards, which would be a good stepping-stone to learning other languages.

The BI developers have been looking for a good OOP implementation for a while now, and they're tried it themselves but it's never been fully functional.

---------- Post added at 12:57 ---------- Previous post was at 12:55 ----------

Just downloaded the example that is some nice work, but I can kinda see what Johnson11B2P is saying though, and i get where Iceman77 is coming from.

I think they (community) would appreciate it if you could provide a simple missions, with a simple explanation to assist (I dont mean the actual OOP principles, but how to implement your classes once created).

I actually think that if this could be utilised correctly (and with enough support), this would definitely improve the scripting levels in Arma. And it may assist those wanting to learn SQF by following website like i posted, which then offers a greater volume of tutorials etc. And more importantly a greater understanding of coding.

I've not programmed in PHP before, but i think ill take a look just to see where this leads. However i can see its fairly straight forward if you have OOP understanding and experience.

All in all - Nice work Keep it up

Thank you Mikie, yes for now all I have up are the two "success stories", the AI caching and distribution one being the best use of OOP, but it is still very complicated.

I have a few more projects I need to wrap-up, but once I'm done with them I'll be able to focus on getting the community to higher scripting standards, with OOP and better tutorials.

Share this post


Link to post
Share on other sites

You got to remember we are pretty limited in what we can make in ARMA. It's not like we are creating something in Java or Objective C where we have a humongous API.

Share this post


Link to post
Share on other sites
You got to remember we are pretty limited in what we can make in ARMA. It's not like we are creating something in Java or Objective C where we have a humongous API.

Front-end code (like simple code to move/spawn units, interface with the GUI, interact with the player, etc.) probably won't see a benefit to this because, like you said, it's limited in its scope.

But when you start to look at some of the back-end code (for instance CBA, ACRE, TFA, ACE in Arma 2, ALiVE in Arma 3, my caching system), huge data structures start to evolve from these total conversions of what BI had done, and you get programming that just doesn't make sense to do in a functional manner. This is where this script is targeted at, not the simple "let's spawn some AI" or "let's make a system for launching artillery", it's targeted at the scripts that plan to create entire systems from nothing, building upon the basic primitives that BI has laid out for us.

So the functionality of this system really depends on who you are and what you do - if you're a simple mission maker, you won't find much use, but if you want to make a good script-based addon you'll see large possibilities for it.

Share this post


Link to post
Share on other sites

I'm trackin a little now. This is meant for someone who already knows Data Structures and Big-O notation.

Share this post


Link to post
Share on other sites
I'm trackin a little now. This is meant for someone who already knows Data Structures and Big-O notation.

For now, unless you want to dedicate some time to learning OOP. But when you come from another language, it's almost given that you know at least the basics of OOP, so hopefully that's a large percentage of the serious developer base.

When I have some time I'll continue making my tutorials for SQF, hopefully including detailed instructions and descriptions on the use and effectiveness of OOP so anyone can use it properly.

Share this post


Link to post
Share on other sites

Object Oriented Design, I hated that class in Objective C but now I rather have that then PHP so I can understand the code.

Share this post


Link to post
Share on other sites

hi :)

I just try the example into the oop.h, i added thoses lines in my init.sqf:

#include "oop.h"

CLASS("PlayerInfo")
	PRIVATE STATIC_VARIABLE("scalar","unitCount");
	PRIVATE VARIABLE("object","currentUnit");
	PUBLIC FUNCTION("object","constructor") {
		MEMBER("currentUnit",_this);
		private ["_unitCount"];
		[b][color="#FF0000"]_unitCount = MEMBER("unitCount",nil);[/color][/b]
		_unitCount = _unitCount + 1;
		MEMBER("unitCount",_unitCount);
	};
	PUBLIC FUNCTION("","getUnit") FUNC_GETVAR("currentUnit");
	PUBLIC FUNCTION("","setUnit") {
		MEMBER("currentUnit",_this);
	};
	PUBLIC FUNCTION("string","deconstructor") {
		DELETE_VARIABLE("currentUnit");
		private ["_unitCount"];
		_unitCount = MEMBER("unitCount",nil);
		_unitCount = _unitCount - 1;
		MEMBER("unitCount",_unitCount);
		hint _this;
	};
ENDCLASS;

_playerInfo = ["new", player] call PlayerInfo;
_currentUnit = "getUnit" call _playerInfo;

it gave me an error:

_unitCount = _unitCount + 1;
([_classID, "unitCount",>
 Error position: <_unitCount + 1;
([_classID, "unitCount",>
 Error Variable indéfinie dans une expression: _unitcount

it would be nice to fix the example code :D I think there is a problem cause i also tried to replace the line by this:

_unitCount = MEMBER("unitCount", 0);

and same message happens :(

Edited by code34

Share this post


Link to post
Share on other sites
hi :)

I just try the example into the oop.h, i added thoses lines in my init.sqf:

#include "oop.h"

CLASS("PlayerInfo")
	PRIVATE STATIC_VARIABLE("scalar","unitCount");
	PRIVATE VARIABLE("object","currentUnit");
	PUBLIC FUNCTION("object","constructor") {
		MEMBER("currentUnit",_this);
		private ["_unitCount"];
		[b][color="#FF0000"]_unitCount = MEMBER("unitCount",nil);[/color][/b]
		_unitCount = _unitCount + 1;
		MEMBER("unitCount",_unitCount);
	};
	PUBLIC FUNCTION("","getUnit") FUNC_GETVAR("currentUnit");
	PUBLIC FUNCTION("","setUnit") {
		MEMBER("currentUnit",_this);
	};
	PUBLIC FUNCTION("string","deconstructor") {
		DELETE_VARIABLE("currentUnit");
		private ["_unitCount"];
		_unitCount = MEMBER("unitCount",nil);
		_unitCount = _unitCount - 1;
		MEMBER("unitCount",_unitCount);
		hint _this;
	};
ENDCLASS;

_playerInfo = ["new", player] call PlayerInfo;
_currentUnit = "getUnit" call _playerInfo;

it gave me an error:

_unitCount = _unitCount + 1;
([_classID, "unitCount",>
 Error position: <_unitCount + 1;
([_classID, "unitCount",>
 Error Variable indéfinie dans une expression: _unitcount

it would be nice to fix the example code :D I think there is a problem cause i also tried to replace the line by this:

_unitCount = MEMBER("unitCount", 0);

and same message happens :(

Sorry for the long wait, I subscribed to this thread but never got a notification that you had posted... Weird :P

Anyways it works as intended, since calling a variable member with a nil parameter makes the member macro retrieve that variable's value - as I'm sure you know. But when the variable isn't initialized (static variables only need to be initialized once, while normal variables will need to be initialized each time an object is created), it will return nil, so this code would be necessary (and it looks like my example doesn't work the way it should haha):

#include "oop.h"

CLASS("PlayerInfo")
PRIVATE STATIC_VARIABLE("scalar","unitCount");
PRIVATE VARIABLE("object","currentUnit");
PUBLIC FUNCTION("object","constructor") {
	MEMBER("currentUnit",_this);
	private ["_unitCount"];
	_unitCount = MEMBER("unitCount",nil); // Retrieves value
	if (isNil "_unitCount") then {_unitCount = 0}; // Initializes variable on a one-time basis
	_unitCount = _unitCount + 1; // Increments variable
	MEMBER("unitCount",_unitCount); // Sets variable back
};
PUBLIC FUNCTION("","getUnit") FUNC_GETVAR("currentUnit");
PUBLIC FUNCTION("","setUnit") {
	MEMBER("currentUnit",_this);
};
PUBLIC FUNCTION("string","deconstructor") {
	DELETE_VARIABLE("currentUnit");
	private ["_unitCount"];
	_unitCount = MEMBER("unitCount",nil);
	_unitCount = _unitCount - 1;
	MEMBER("unitCount",_unitCount);
	hint _this;
};
ENDCLASS;

_playerInfo = ["new", player] call PlayerInfo;
_currentUnit = "getUnit" call _playerInfo;

Anyways I just fixed the example in Github too - thanks for bringing this up to me.

Edited by Naught

Share this post


Link to post
Share on other sites

it could be more sexy to use something like this :

#define new(argument1,argument2) ['new', argument2] call argument1
#define delete(argument1,argument2) ['delete', argument2] call argument1

_playerInfo = ["new", player] call PlayerInfo;
["delete", _playerInfo, "Player Removed!"] call PlayerInfo;

becomes

_playerInfo = new(PlayerInfo, player);
delete(_playerInfo, "player removed");

Share this post


Link to post
Share on other sites
it could be more sexy to use something like this :

#define new(argument1,argument2) ['new', argument2] call argument1
#define delete(argument1,argument2) ['delete', argument2] call argument1

_playerInfo = ["new", player] call PlayerInfo;
["delete", _playerInfo, "Player Removed!"] call PlayerInfo;

becomes

_playerInfo = new(PlayerInfo, player);
delete(_playerInfo, "player removed");

Yes it would make the code look more natural, but then you wouldn't be able to pass arrays unless you define them as a variable prior, which would be wasting memory (macros can't take arrays as parameters because they contain commas).

Also it would require including the "oop.h" file in every script that called the object, and I was hoping to be able to keep the layout of including the "oop.h" file only where objects/classes are defined, so they can be created, accessed, manipulated, and destroyed anywhere after that with normal SQF syntax. Not to mention the include system is totally broken with a non-working #include "../file.sqf" command.

And although I can just add those macros to the "oop.h" file to be used as a shorthand alternative, I feel like it would create more confusion than benefit (especially with the whole no-array thing).

But good idea nonetheless, I'd love to be able to simplify the syntax a bit if possible.

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

×