naught 6 Posted November 4, 2013 (edited) 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 AI Caching and Distribution Script Object-Oriented Hashmaps Object-Oriented Marker Edited January 26, 2014 by Naught Minor updates Share this post Link to post Share on other sites
Hypnomatic 10 Posted November 4, 2013 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
mikie boy 18 Posted November 4, 2013 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
naught 6 Posted December 31, 2013 Updated to v1.3.1, read the original post for updated information. Share this post Link to post Share on other sites
iceman77 18 Posted December 31, 2013 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
iceman77 18 Posted January 1, 2014 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
code34 248 Posted January 1, 2014 (edited) 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 January 1, 2014 by code34 Share this post Link to post Share on other sites
mikie boy 18 Posted January 1, 2014 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 Share this post Link to post Share on other sites
iceman77 18 Posted January 1, 2014 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
naught 6 Posted January 1, 2014 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
iceman77 18 Posted January 1, 2014 Cool. Thanks for the example. Share this post Link to post Share on other sites
Johnson11B2P 3 Posted January 1, 2014 Who is your main target for creating this OOD? I believe this is way over most people's head. Share this post Link to post Share on other sites
dr_strangepete 6 Posted January 1, 2014 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
mikie boy 18 Posted January 1, 2014 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
naught 6 Posted January 1, 2014 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
Johnson11B2P 3 Posted January 1, 2014 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
naught 6 Posted January 1, 2014 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
Johnson11B2P 3 Posted January 1, 2014 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
naught 6 Posted January 1, 2014 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
Johnson11B2P 3 Posted January 1, 2014 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
code34 248 Posted January 4, 2014 (edited) 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 January 4, 2014 by code34 Share this post Link to post Share on other sites
naught 6 Posted January 7, 2014 (edited) 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 January 7, 2014 by Naught Share this post Link to post Share on other sites
code34 248 Posted January 8, 2014 yes it works now , thks ;) Share this post Link to post Share on other sites
code34 248 Posted January 8, 2014 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
naught 6 Posted January 8, 2014 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