PDA

View Full Version : Classes (OO)


Y_Less
16/07/2012, 10:52 AM
So I think I cracked it last night! Based on re-reading one of the earliest comments in my inline functions thread - at the time I discarded it because it was wrong, but then I realised it doesn't have to be wrong!


// This is loosely based on JavaScript objects -
// the container IS the constructor (as a side-effect,
// you can also use the little-know JavaScript ability
// of returning a different object from a constructor
// instead of creating a new one):
// http://backstage.soundcloud.com/2012/06/building-the-next-soundcloud/
// (Sharing Models between Views)
class CObject()
{
new
mModel,
Float:mX,
Float:mY,
Float:mZ;

method Move(Float:x, Float:y, Float:z)
{
mX = x;
mY = y;
mZ = z;
}
}


This is based around macros SOMETHING LIKE (I've not written them yet, and you can't actually assign inline functions like this yet):


// The "()" indicates the constructor (and can have parameters).
#define class%0() \
stock %0:%0() \
for (new __loop=0, __memory = 0, __methods[128]; ; ++__loop) \
if (__loop) return %0:SomehowAllocate(#%0, __memory); \
else


The "__memory" parameter keeps a track of all the memory allocated, to be allocated using "y_malloc". The loop means that we can put the return statement at the top of the constructor. "__methods" (or something similar, still thinking about this) keeps track of all the methods somehow - the first time this constructor is called a record of the class' static elements should be built up somewhere. Ideally, we don't want to start allocating memory for things before "SomehowAllocate" in case the user doesn't want this instance. It may be required to add another loop after "else" just to put in some extra padding variables - that way we can expand the stack by 24 bytes - enough to cover the "SomehowAllocate" function header, without clobbering any of the local variables that have now been removed from the stack (but still exist in garbage memory).

Note the "%0:" to set a tag the same name as the class.

"static" variables nicely work as you would expect - they are shared amongst all instances of the same class!


#define method%0(%1) \
__methods[__indexIforgot++] = inline%0(%1)


This just stores the record of the inline function somewhere and is used in a very similar manner to "y_inline", but I need a better way of calling them. Again, this is the ideal:


new
CObject:obj = CObject();
obj.Move(0.0, 0.0, 0.0);


However, that is impossible without defining "obj" as a macro too, and that's too many extra steps. I was thinking of something along the lines of:


new
CObject:obj = CObject();
obj _ Move(0.0, 0.0, 0.0);


It's a little ugly, but better (the spaces would be required). This would translate to something like:


#define _%0(%1) \
- \
FindMethod(#%0, %1)


(There are ways to handle zero parameter functions I've left out) "FindMethod" would return a list of all methods in all classes matching that name, and an overloaded "-" operator (needs to be added to the class definition) would traverse that list looking for the correct one (could even use code rewriting to improve subsequent calls).


stock operator-(%0:obj, __Methods:m)
{
// Loop somehow based on the __Methods structure.
}


This would also restrict all functions to returning ints ("_:", or nothing), but returning anything else would require major changes to "y_inline" anyway:


method GetX()
{
return _:mX;
}

new
Float:x = Float:(obj _ GetX());


I also toyed with syntax like "-@#" for objects, but that's just horrible (but I like the idea of using "-" somewhere). Actually, "-c" looks quite nice and is workable.

Edit: Even better (maybe):


obj o Method()


That quite appeals to me as it looks like the presentation formatted version of function composition in Haskell ("." in written Haskell). That has the additional advantage of not screwing up the "_:" macro in YSI (ironically, used to detect zero-argument functions and fix them, so given what I said earlier that can't really get screwed up).

[MM]RoXoR[FS]
16/07/2012, 11:59 AM
So probably in future we can do like this

class pData
{
new
Name[MAX_PLAYER_NAME],
Kills;
}pInfo[MAX_PLAYERS];

new File:ini=fopen("data.txt", io_binary|io_write|io_read);
ini.write((char *)&pInfo[playerid],sizeof(pInfo[playerid]));

ini.read((char *)&pInfo[playerid],sizeof(pInfo[playerid]));

Y_Less
16/07/2012, 12:05 PM
No, did you not read the post?

[HLF]Southclaw
16/07/2012, 06:00 PM
Wow this is interesting!
I still haven't learnt totally how OOP/Classes work in languages like Java and C++ (It's next on my to-learn list)

I sort of understand how "inline" functions work, I read the topic on inline functions and wondered what the advantages were, but I think I need to understand it more first...



But, what are the advantages of using this classes "system" in pawn?


Oh and Great work on this! :p

Grim_
17/07/2012, 04:12 AM
Wow, this is very nice, I can't wait until you finalize it!

You mentioned the container would be the constructor, so does this mean we wouldn't be able to add two constructors? Would I have to make two classes that do the same thing? Also, would it be necessary to create/define the functions inside the class like that (I'm guessing so)?

Slice
17/07/2012, 06:23 AM
Interesting!

I'm not sure how exactly you meant to use the operator, but wouldn't it complain that the "expression has no effect"?

One way to work around this would be to use the assignment operator instead. You could increase the return address while inside the operator to skip the following "STOR(.S).pri", though you'd have to read the address of "obj" from there as well. Like I did in this experiment (http://forum.sa-mp.com/showthread.php?p=1945021).

Because of the way methods will be called, perhaps tagging the variables with the class name isn't needed - it wouldn't give warnings for tag mismatches or anything anyway. One really cool thing could be to simply tag them as "Instance", that way allowing true polymorphism (and people wouldn't need to create an operator along with their classes).

Caching calls
What you could do is something like this:

#define _%0(%1) \
- \
FindMethod(!"\0\0\0\0\0\0\0\0" #%0, %1)


The first byte represents the address of an instance (its allocated memory, I suppose), and the second byte represents the direct address to a class method. If the 1st byte is unequal to the instance's address, search for the method in the instance's class and store it at the 2nd byte, then store the instance's address at the 1st byte. If the 1st byte equals to the instance, then the 2nd will be the address of the method.
This will make the calls cached, so they'd be significantly faster in loops and such.

Memory management

I'm not sure if you've thought about this, but this opens the possibility for memory leaks (which I know lots of people will have problems with).

I would suggest reference counting ( la objc) with 3 key functions: allocate, retain, (auto)release.

Basically, instances have a reference count (retain count). The general idea is when something stores an instance in a variable, it should increase the count by 1, and when it doesn't need the instance anymore it should decrease the count by 1.
When the count hits 0, the instance is deallocated.

When an instance is created it will be retained (so its reference count is 1), but also autoreleased, which adds its address to an autorelease pool that will be called by a 0ms timer. This way users won't have to worry too much about this.
If the users want to keep an instance around for longer, however, the instance must be retained.

The autorelease pool will simply release the instances, lowering the retain count by 1. If the instance has a higher retain count than 1, it will still be alive.

I would suggest a debug mode in which deallocated instances would still have around 3 bytes at the address they were allocated, containing a simple NULL indicating they have a deallocated object. This would later generate a warning in the console.

Giovanni
17/07/2012, 01:33 PM
Woah sweet! But that the container is the constructor is something I have to get used to. Is there any chance that you can do this like in Java or C++ so that you have an actual constructor?

Southclaw;1991172']
But, what are the advantages of using this classes "system" in pawn?


You can develope big scripts in a shorter amount of time I guess.

Y_Less
17/07/2012, 01:46 PM
Interesting!

I'm not sure how exactly you meant to use the operator, but wouldn't it complain that the "expression has no effect"?

One way to work around this would be to use the assignment operator instead. You could increase the return address while inside the operator to skip the following "STOR(.S).pri", though you'd have to read the address of "obj" from there as well. Like I did in this experiment (http://forum.sa-mp.com/showthread.php?p=1945021).


I hadn't thought of the "no effect" problem! Assignment would make more sense, and I don't need to skip the next instruction (it may be an array lookup so longer than one instruction), just return the same object.

Because of the way methods will be called, perhaps tagging the variables with the class name isn't needed - it wouldn't give warnings for tag mismatches or anything anyway. One really cool thing could be to simply tag them as "Instance", that way allowing true polymorphism (and people wouldn't need to create an operator along with their classes).

It would given warnings for some variables, just not all (any class object could try to use any method, but will just fail often). And I would put the operator in the main "class" macro so uses don't need to worry about it.

Caching calls
What you could do is something like this:

#define _%0(%1) \
- \
FindMethod(!"\0\0\0\0\0\0\0\0" #%0, %1)


The first byte represents the address of an instance (its allocated memory, I suppose), and the second byte represents the direct address to a class method. If the 1st byte is unequal to the instance's address, search for the method in the instance's class and store it at the 2nd byte, then store the instance's address at the 1st byte. If the 1st byte equals to the instance, then the 2nd will be the address of the method.
This will make the calls cached, so they'd be significantly faster in loops and such.

I like that (it's very similar to what y_inline actually does already).

Memory management

I'm not sure if you've thought about this, but this opens the possibility for memory leaks (which I know lots of people will have problems with).

I would suggest reference counting ( la objc) with 3 key functions: allocate, retain, (auto)release.

Basically, instances have a reference count (retain count). The general idea is when something stores an instance in a variable, it should increase the count by 1, and when it doesn't need the instance anymore it should decrease the count by 1.
When the count hits 0, the instance is deallocated.

When an instance is created it will be retained (so its reference count is 1), but also autoreleased, which adds its address to an autorelease pool that will be called by a 0ms timer. This way users won't have to worry too much about this.
If the users want to keep an instance around for longer, however, the instance must be retained.

The autorelease pool will simply release the instances, lowering the retain count by 1. If the instance has a higher retain count than 1, it will still be alive.

I would suggest a debug mode in which deallocated instances would still have around 3 bytes at the address they were allocated, containing a simple NULL indicating they have a deallocated object. This would later generate a warning in the console.

I think I see what you mean, so explicitly mark objects that are to be kept around?

Woah sweet! But that the container is the constructor is something I have to get used to. Is there any chance that you can do this like in Java or C++ so that you have an actual constructor?



You can develope big scripts in a shorter amount of time I guess.

Not that I can think of. But as I said, this is based on one method of doing objects in JavaScript. You don't really need multiple constructors anyway when you have optional arguments.

Slice
17/07/2012, 02:29 PM
Assignment would make more sense, and I don't need to skip the next instruction (it may be an array lookup so longer than one instruction), just return the same object.
If you return the instance address then methods can't return things.
Looking at the PAWN source, the following instructions will be POP.alt + STOR.I for arrays and STOR(.S).pri for cells.
Using -= might be a better idea, as you would get the instance address in that (as opposed to =, which only gives you the new value). The trailing opcodes look the same as =.

new something = obj -= FindMethod(...); // calls oper(obj, FindMethod(...))

It would given warnings for some variables, just not all (any class object could try to use any method, but will just fail often). And I would put the operator in the main "class" macro so uses don't need to worry about it.
How would it fail? I mean, how can the compiler let other functions see what's inside the class's wrapper function?

I think I see what you mean, so explicitly mark objects that are to be kept around?
Well, in a way. They won't be marked simply as keep/not keep.

Example:

main() {
new TDClass:td = TDClass(); // retain count = 1 (autoreleased)

KeepAlive(td, 1); // retain count = 2
KeepAlive(td, 2); // retain count = 3


} // autorelease pool kicks in, retain count = 2

// first time this is called, retain count becomes 1
// second time this is called, retain count becomes 0. instance deallocated.
public ReleaseObject(obj) {
obj o Release();
}

stock KeepAlive(TDClass:td, seconds) {
td o Retain(); // retain count++

SetTimerEx("ReleaseObject", seconds * 1000, "i", _:td);
}


I hope you get what I mean. The good thing about this is it doesn't make a one place decide when to deallocate the object, it simply disappears when nobody wants it anymore!

Y_Less
17/07/2012, 04:54 PM
If you return the instance address then methods can't return things.
Looking at the PAWN source, the following instructions will be POP.alt + STOR.I for arrays and STOR(.S).pri for cells.

Yeah, I'll want to check a few more instances of code but I do recall seeing that as how they saved in the past.

Using -= might be a better idea,

Already thought of that too since your last post :D.

as you would get the instance address in that (as opposed to =, which only gives you the new value). The trailing opcodes look the same as =.

new something = obj -= FindMethod(...); // calls oper(obj, FindMethod(...))

Agreed. I'm still trying to figure out HOW to return from methods. In fact, the returns issue is a good argument for no tags on class constructors, because doing this will actually make the compiler complain:


class A() // Returns "A:" tag.
{
method B()
{
return 8; // No tag, but a return technically inside "A".
}
}


Also, skipping the assignment operator solves another problem with the implementation (though I have other ideas for that). Currently y_inline uses "SCTRL 6" to return from inline functions, but that needs "pri", which is where returns are stored and got from.

How would it fail? I mean, how can the compiler let other functions see what's inside the class's wrapper function?

Because methods are actually looked up by string name, so you can pass any object any method, whether it exists for that class or not, and the compiler can't know any different. I did try to think of a way to solve it but I've not got one yet.

Well, in a way. They won't be marked simply as keep/not keep.

OK, much clearer thanks!

Slice
17/07/2012, 05:19 PM
Well, if you did use a generic tag for instances instead of the class it would solve some problems.

For example, you could create operators for assigning a value tagged "Instance:" to both floats and ints seamlessly.

The only downside would be functions can't be strict on what type of instance they want.

Y_Less
17/07/2012, 05:39 PM
That is a very good thought!

[HLF]Southclaw
19/07/2012, 05:55 PM
I'm finally learning about OOP (It's quite simple... no idea why I avoided it before assuming it was complex)

I just want to confirm a bit how this would be used in Pawn/SA:MP.

So for instance, I have some simple Create/Destroy script (Like objects (models, not OOP objects)) would it go something like this:



// Question: Where would this be declared/initiated?
// Could it just be intiated outside of code or would it have to be in a function of some sort
// Like main() or OnGamemode/FilterScriptInit?

class Item()
{
new
Float:iPosX,
Float:iPosZ,
Float:iPosY,
iState,
iSize;

method setPos(Float:x, Float:y, Float:z)
{
if(z > 100)return print("Too high");
iPosX = x;
iPosY = y;
iPosZ = z;
return 1; // I assume returns would work as expected
}

method setState(state)
{
if(state == 0 || state == 1 || state == 2 || state == 3)
{
iState = state;
}
else print("State error");
}

method setSize(size)
{
if(2 < size < 10)iSize = size;
print("Size error");
}
}

public OnFilterScriptInit()
{
new Item:myItem = Item();

myItem _ setPos(1.0, 2.0, 5.0);
myItem _ setState(5); // Would print an error
myItem _ setSize(7);
}

// What about inheriting? Is that possible or just out of the question?

class ItemVersion2 extends Item()
{
new
bool:isFlying,
bool:isSwimming,
bool:isRunning;

method setRun()
{
isRunning = !isRunning;
}
// etc...
}

public OnSomething()
{
new ItemVersion2:mySecondItem = ItemVersion2();

mySecondItem _ setPos(2.0, 2.0, 4.0); // From 'Item'
mySecondItem _ setRun();

// Also, is direct variable changing possible?
// Maybe not for the inherited variables, just in general

mySecondItem _ isFlying = true;
}


Sorry for all the questions, I'm probably just thinking too far out the box! I'm kind of new to OOP and I'm not entirely sure how you do these things! But for all I know it could be possible, you've done some pretty good things with Pawn that I would never have thought to be possible :p


And I'm glad I understand how OOP/Classes work now, I can appreciate this even more!

Y_Less
19/07/2012, 06:17 PM
Yes, but there's no "extends" currently - I can't think of a way to do that at all. The closest there is would be membership instead.

Y_Less
03/10/2012, 01:12 PM
I'm moving some files about so I thought I would post what little code I had written ages ago here:


// VERY EXPERIMENTAL AND INCOMPLETE CODE! DO NOT USE CURRENTLY!

#include "y_malloc"
#include "y_inline"

#define method%0(%1) OO_Add(#%0);inline%0(%1)

enum E_OO_CLASS_DATA
{
E_OO_CLASS_DATA_NAME[32 char],
//E_OO_CLASS_DATA_METHOD_START,
E_OO_CLASS_DATA_SIZE
}

enum E_OO_METHOD_DATA
{
//E_OO_METHOD_DATA_NAME[32 char],
//E_OO_METHOD_DATA_SPECIFIER,
E_OO_METHOD_DATA_PARENT,
// Point to the next method with the same name. Methods can NEVER be
// removed (they're static compiled code), so we can just loop through the
// list linerarly.
E_OO_METHOD_DATA_SAME,
// Store the y_inline data locally.
E_OO_METHOD_DATA_INLINE[E_CALLBACK_DATA]
//E_OO_METHOD_DATA_PTR
}

#define MAX_OO_METHODS (16)
#define MAX_OO_CLASSES (16)

static stock
// Temporary storage to put stack variables in.
YSI_gsMethods,
YSI_gsStack[256],
YSI_gsClassData[MAX_OO_CLASSES][E_OO_CLASS_DATA],
YSI_gsMethodData[MAX_OO_CLASSES * MAX_OO_METHODS][E_OO_METHOD_DATA];

stock OO_StoreParams(GLOBAL_TAG_TYPES:...)
{
// This function was surprisingly easy to write! As was the required
// y_inline "Callback_Array" function - I added just ONE "LOAD.I", but now
// public functions don't work through that system (not that I really care -
// they make no sense in OO anyway).
new
num = numargs() - 1,
from,
to;
#emit LCTRL 5
#emit ADD.C 12
#emit STOR.S.pri from
#emit CONST.pri YSI_gsStack
#emit STOR.S.pri to
while (num--)
{
#emit LREF.S.pri from
#emit SREF.S.pri to
from += 4;
to += 4;
}
}

static stock OO_GetClass(that)
{
return 0;
}

stock operator-(CTest:that, __Methods:matches)
{
new
cls = OO_GetClass(that);
while (matches != __Methods:-1)
{
if (YSI_gsMethodData[_:matches][E_OO_METHOD_DATA_PARENT] == cls)
{
// Found the correct matching function name.
// TODO: Code rewriting.
Callback_Array(YSI_gsMethodData[_:matches][E_OO_METHOD_DATA_INLINE], YSI_gsStack);
return 0;
}
matches = __Methods:YSI_gsMethodData[_:matches][E_OO_METHOD_DATA_SAME];
}
// Didn't find the correct method.
return cellmin;
}

//stock OO_

stock OO_Add(const string:method[])
{
new
result[E_CALLBACK_DATA];
//if (Callback_Get(callback_tag:method, YSI_gsMethodData[YSI_gsMethods][E_OO_METHOD_DATA_INLINE])
if (Callback_Get(callback_tag:method, result))
{
printf("Found method %s", method);
}
}



CTest:CTest()
{
for (new __memory = 0, __loop; ; ++__loop)
{
if (__loop)
{
// Allocate the data.
return;
}
else
{
for (new CTest:__this, __padding[5]; !__this; ++__this)
{
// This is where the main constructor code goes.
new
mVar1 = 10,
mVar2 = 20,
mVar3 = 30;

static
smInAllClasses = 40;

method AddOne()
{
mVar1 += 1;
}

method AddTwo()
{
mVar2 += 2;
}
printf("loop");
}
}
}
}

/*
#define _DEBUG 7

#include <a_samp>
#include <YSI\y_malloc>
#include <YSI\y_inline>
#include <YSI\y_oo>

Get(callback:callback, ...)
{
new
result[E_CALLBACK_DATA],
arr[] = {44, 45, 46},
num = numargs() - 1,
tmp,
addr;
#emit LCTRL 5
#emit ADD.C 16
#emit STOR.S.pri tmp
#emit ADDR.pri arr
#emit STOR.S.pri addr
while (num--)
{
#emit LREF.S.pri tmp
#emit SREF.S.pri addr
tmp += 4;
addr += 4;
}
Callback_Get(callback, result);
Callback_Call(result, 42, 43, 44);
Callback_Array(result, arr);
}

main()
{
print("\n----------------------------------");
print(" Blank Gamemode by your name here");
print("----------------------------------\n");
inline Hello(a, b, c)
{
printf("%d %d %d", a, b, c);
}
Get(using inline Hello, 77, 78, 79);
CTest();
}
*/


The commented stuff at the end (IIRC) does actually work, but I can't remember what I was doing at the minute. The "CTest" "class" is currently expanded and contains the code that the macros should end up with.

Gamer_Z
04/02/2013, 09:21 AM
this stuff is very cool, but isn't it -by the time you create all this - easier/faster to just make a custom compiler which generates sa-mp compatible output(amx) (while maintaining compatibility with current scripts)?

SA-MP won't change the pawn version anymore, At least I think so.

I have a failed attempt at a custom pre-processor.. but it's not a compiler so there is a difference.

Y_Less
04/02/2013, 10:27 AM
A compiler is A LOT of work - serious amounts! I would have liked to have written an LLVM front/back end for PAWN/P-code, but don't have the time.

Gamer_Z
04/02/2013, 10:39 AM
A compiler is A LOT of work - serious amounts! I would have liked to have written an LLVM front/back end for PAWN/P-code, but don't have the time.

Hmm my words sounded a bit too big I think, I had in mind modifing the current pawn compiler :P
I read about LLVM on wiki but I still don't understand it. What's it all about? Standalone compiler or something that generates some output and passes the remaining stuff onto the original compiler?

Blackazur
04/02/2013, 10:52 AM
That is nice.

Meta
04/02/2013, 10:59 AM
This looks nice :o

Y_Less
04/02/2013, 11:05 AM
LLVM is a framework - it has a well defined intermediate language (i.e. an internal programming language) and multiple independent stages (mostly optimisation stages) each of which take input in the form of that language and output in the same form. Unlike something like GCC, the stages are VERY decoupled - in GCC if you modify one part you have to be very careful not to seriously affect other parts. The use of the common intermediate format means that if you can write a front-end to convert your language to this common language then you get all the other stages for free, including code generation for a wide range of targets. Unfortunately, while things like ARM and x86 ARE back-ends, p-code (amx) isn't so you would need to write that too.

steki.
04/02/2013, 11:05 AM
I have been thinking about making this from a long time, glad someone solid-minded took this over.
I was thinking about a custom preprocessor, doing something very similar to Object Orientation o C Language (http://www.planetpdf.com/codecuts/pdfs/ooc.pdf) and pointers, memory management, whatever comes to my mind.

Good luck on this!

Y_Less
04/02/2013, 11:09 AM
Pointers and memory management won't work no matter how good the pre-processor because they aren't in the base language (at least not really - they can be faked).

steki.
04/02/2013, 11:34 AM
Pointers and memory management won't work no matter how good the pre-processor because they aren't in the base language (at least not really - they can be faked).

I mean code -> preprocessor -> pawn script + #emit codes -> pawn compiler.
Everything is faked.

Slice
16/04/2013, 10:20 PM
We could use Prawn to generate the code and include a runtime written in Pawn.

Something like this:
First look for "class x {}" and re-structure the inside. Move the whole thing to a separate file and put class/instance variables in the global scope.
Store information about all methods and save them prefixed with something short (C1_, C2_, ..).
Figure out a good way to replace method calls and such.
Hook the whole thing up to a runtime that will deal with allocation and alll that fun stuff.


More flexible alternative:
Methods are not renamed (no need).
There's a public init function that will be automatically run by the runtime. It willl send over its method addresses and their their names.
It would search for patterns such as "a->b", "a::b", etc. These patterns would be replaced with something like InvokeMethod(instance, name_idx). name_idx being the index of some large method name array.
Each class file would have a static variable called "this", which will be modified by InvokeMethod (via a stack).
Instances are addresses to blocks of memory. The block contains information about its class and room for instance variables.
There's no hard typing. You could invoke any method on any class (perhaps an exception should be thrown, wink wink).
Instance variables will be pointers. Enums and such will have 1-length arrays created for them. Fast pointers , simply NOP out the BOUNDS opcodes.

This makes it so you don't need data types for instances - instances could hold different types of classes.
[/braindump]



What do you think?

Y_Less
16/04/2013, 10:25 PM
Err, I'll have to think a lot more about what you just said before I'm entirely sure.

One thing did get my attention though:

NOP out the BOUNDS opcodes.

I'm tempted to write something to do this regardless - scan the whole COD segment for these and just remove them, or at least do it within certain library sections.

Slice
16/04/2013, 10:37 PM
You could scan the COD for a certain address (global pointer variable), change it to the start of DAT, modify LIDX and nop BOUNDS. After that, just go crazy with g_Unsafe[address] (or something).

Y_Less
17/04/2013, 03:56 PM
I'm trying to figure out how this (BOUNDS NOPing) can be used effectively with y_malloc, especially if I decide to switch to using the heap instead of a huge array. Currently the new version of YSI allocates a the memory statically, which seriously and noticeably affects both compile times and .amx file sizes!

I was going to say that this would require me to very reliably be able to identify which "BOUNDS" OpCode was associated with the underlying data store and not get incorrect results from code like this:


gArr1[gArr2[gArr3[7]]];


But in theory if I'm not sure then I can just blank the additional ones too (the only issue is the adverse effects on debugging data). And once that is done the "Alloc:" handle I return can just be an offset such that doing:


new gMallocArr[2];
gMallocArr[ptr];


Neatly points straight in to the heap.

Y_Less
26/04/2013, 10:00 PM
I've got an even better idea for the syntax! I realised I can do this:


obj-->method();


Almost identical to C++'s "->" but not quite (that one can't be done unfortunately, I did think about it).

Slice
26/04/2013, 10:15 PM
You'd need either functions or macros for the methods, I suppose.

Maybe if you made method declarations like "#define methodName METHOD(class, methodName)" it would be a lot easier, but it doesn't feel right. :(

Edit:
What'd you think the best code layout is? I did some scribbling:

main() {
new db = new SQLite("test.db");
new r = db->query("SELECT `something` FROM `test`");
new buf[128];

r->getField(0, buf);

print(buf);
}

class SQLite {
private DB:db;
private filename[];
public bool:throwErrors = false;

public construct(const filename[]) {
strpack(this->filename, filename);

this->openDb();
}

public destruct() {
this->closeDb();
}

private openDb() {
if (this->db) {
return true;
}

if (this->db = db_open(filename)) {
return true;
}

if (this->throwErrors) {
throw new Error("Unable to open the database");
}

return false;
}

private closeDb() {
if (this->db) {
db_close(this->db);

this->db = DB:0;
}
}

public query(const query[]) {
new DBResult:r;

if (!this->openDb()) {
return NULL;
}

r = db_query(this->db, query);

if (!r) {
if (this->throwErrors) {
throw new Error(
sprintf("Query failed: %s", db_errmsg(this->db)),
db_errno(this->db)
);
} else {
return NULL;
}
}

return new SQLiteResult(r);
}
}

class SQLiteResult {
private DBResult:result;

public construct(DBResult:result) {
this->result = result;
}

public destruct() {
this->free();
}

public getField(field = 0, result[], maxsize = sizeof(result)) {
db_get_field(this->result, result, maxsize);
}

public free() {
if (this->result) {
db_free_result(this->result);

this->result = DBResult:0;
}
}
}

Y_Less
27/04/2013, 01:12 AM
Yeah, I need to think some more about the syntax. I have ideas but I'm not sure which are and aren't possible. I've also thought some more and while "-->" is possible in theory there are major issues - the most major being that return values don't work at all as it makes everything "bool:"... Well, most returns would still sort of work, but strings really wouldn't.

Edit: I just reviewed the first post and it reminded me of some things I had forgotten. y_inline can't return strings anyway, so that's a non-restriction. Now there's only one problem with "-->" and I don't think it can be overcome...

Y_Less
29/04/2013, 12:13 AM
OK, some basic proof-of-concept code:

http://pastebin.com/ipbZATrX

Aeonosphere
03/05/2013, 03:16 PM
I think it'd be nice to get PAWN working in some kind of functional paradigm too. Seeing as we all know LISP is the best language ever invented, I might just try it. (no bias whatsoever ;) ).

Slice
03/05/2013, 03:38 PM
OK, some basic proof-of-concept code:

http://pastebin.com/ipbZATrX

I don't think it'll be that easy - method names must be unique (multiple classes) somehow and probably looked-up during runtime.
Perhaps methods could be tagged operators with both the class tag and method tag (which would yield a unique combination).

Y_Less
03/05/2013, 03:40 PM
I'm not sure why they need to be unique - multiple classes can have the same method. This will be combined with the inline code from the first post don't forget (I say "forget", I didn't actually say that in the first place).

Aeonosphere
03/05/2013, 04:35 PM
How are you going to deal with destructors/constructors and are you going to use scoping of any sort to ensure less overheads and more security if something goes out of scope, or?

Y_Less
03/05/2013, 04:39 PM
Scoping and constructors are covered in the first post, and similar to javascript.

Slice
09/09/2013, 11:34 AM
This is still very raw and not really OO, but it can come in handy:
Look at the main function in the bottom.
#include <a_samp>

// Enclose statement
#define ENC: \
(_:E1:E2:E3:E4:E5:E6:E7:E8:E9:E10:E11:E12:E13:E14: E15:E16:E17:E18:0,

#define ENC_END: \
)

#define E1:E2:E3:E4:E5:E6:E7:E8:E9:E10:E11:E12:E13:E14:E15 :E16:E17:E18:0,%1\32;%2 ENC_2:%1 ENC_END:
#define E2:E3:E4:E5:E6:E7:E8:E9:E10:E11:E12:E13:E14:E15:E1 6:E17:E18:0,%1> ENC_2:%1 ENC_END:>
#define E3:E4:E5:E6:E7:E8:E9:E10:E11:E12:E13:E14:E15:E16:E 17:E18:0,%1< ENC_2:%1 ENC_END:<
#define E4:E5:E6:E7:E8:E9:E10:E11:E12:E13:E14:E15:E16:E17: E18:0,%1| ENC_2:%1 ENC_END:|
#define E5:E6:E7:E8:E9:E10:E11:E12:E13:E14:E15:E16:E17:E18 :0,%1& ENC_2:%1 ENC_END:&
#define E6:E7:E8:E9:E10:E11:E12:E13:E14:E15:E16:E17:E18:0, %1^ ENC_2:%1 ENC_END:^
#define E7:E8:E9:E10:E11:E12:E13:E14:E15:E16:E17:E18:0,%1, ENC_2:%1 ENC_END:,
#define E8:E9:E10:E11:E12:E13:E14:E15:E16:E17:E18:0,%1) ENC_2:%1 ENC_END:)
#define E9:E10:E11:E12:E13:E14:E15:E16:E17:E18:0,%1] ENC_2:%1 ENC_END:]
#define E10:E11:E12:E13:E14:E15:E16:E17:E18:0,%1/ ENC_2:%1 ENC_END:/
#define E11:E12:E13:E14:E15:E16:E17:E18:0,%1* ENC_2:%1 ENC_END:*
#define E12:E13:E14:E15:E16:E17:E18:0,%1^ ENC_2:%1 ENC_END:^
#define E13:E14:E15:E16:E17:E18:0,%1% ENC_2:%1 ENC_END:%
#define E14:E15:E16:E17:E18:0,%1- ENC_2:%1 ENC_END:-
#define E15:E16:E17:E18:0,%1+ ENC_2:%1 ENC_END:+
#define E16:E17:E18:0,%1= ENC_2:%1 ENC_END:=
#define E17:E18:0,%1; ENC_2:%1 ENC_END:;
#define E18:0,%1\10;%2 0,%1 ENC_END:

#define ENC_2: \
e1:e2:e3:e4:e5:e6:e7:e8:e9:e10:e11:e12:e13:e14:e15 :e16:e17:0,

#define e1:e2:e3:e4:e5:e6:e7:e8:e9:e10:e11:e12:e13:e14:e15 :e16:e17:0,%1!%2\32;%3ENC_END: e2:e3:e4:e5:e6:e7:e8:e9:e10:e11:e12:e13:e14:e15:e1 6:e17:0,%1 ENC_END:!%2
#define e2:e3:e4:e5:e6:e7:e8:e9:e10:e11:e12:e13:e14:e15:e1 6:e17:0,%1>%2\32;%3ENC_END: e3:e4:e5:e6:e7:e8:e9:e10:e11:e12:e13:e14:e15:e16:e 17:0,%1 ENC_END:>%2
#define e3:e4:e5:e6:e7:e8:e9:e10:e11:e12:e13:e14:e15:e16:e 17:0,%1<%2\32;%3ENC_END: e4:e5:e6:e7:e8:e9:e10:e11:e12:e13:e14:e15:e16:e17: 0,%1 ENC_END:<%2
#define e4:e5:e6:e7:e8:e9:e10:e11:e12:e13:e14:e15:e16:e17: 0,%1|%2\32;%3ENC_END: e5:e6:e7:e8:e9:e10:e11:e12:e13:e14:e15:e16:e17:0,% 1 ENC_END:|%2
#define e5:e6:e7:e8:e9:e10:e11:e12:e13:e14:e15:e16:e17:0,% 1&%2\32;%3ENC_END: e6:e7:e8:e9:e10:e11:e12:e13:e14:e15:e16:e17:0,%1 ENC_END:&%2
#define e6:e7:e8:e9:e10:e11:e12:e13:e14:e15:e16:e17:0,%1^% 2\32;%3ENC_END: e7:e8:e9:e10:e11:e12:e13:e14:e15:e16:e17:0,%1 ENC_END:^%2
#define e7:e8:e9:e10:e11:e12:e13:e14:e15:e16:e17:0,%1;%2\3 2;%3ENC_END: e8:e9:e10:e11:e12:e13:e14:e15:e16:e17:0,%1 ENC_END:;%2
#define e8:e9:e10:e11:e12:e13:e14:e15:e16:e17:0,%1)%2\32;% 3ENC_END: e9:e10:e11:e12:e13:e14:e15:e16:e17:0,%1 ENC_END:)%2
#define e9:e10:e11:e12:e13:e14:e15:e16:e17:0,%1]%2\32;%3ENC_END: e10:e11:e12:e13:e14:e15:e16:e17:0,%1 ENC_END:]%2
#define e10:e11:e12:e13:e14:e15:e16:e17:0,%1/%2\32;%3ENC_END: e11:e12:e13:e14:e15:e16:e17:0,%1 ENC_END:/%2
#define e11:e12:e13:e14:e15:e16:e17:0,%1*%2\32;%3ENC_END: e12:e13:e14:e15:e16:e17:0,%1 ENC_END:*%2
#define e12:e13:e14:e15:e16:e17:0,%1^%2\32;%3ENC_END: e13:e14:e15:e16:e17:0,%1 ENC_END:^%2
#define e13:e14:e15:e16:e17:0,%1%%2\32;%3ENC_END: e14:e15:e16:e17:0,%1 ENC_END:%%2
#define e14:e15:e16:e17:0,%1-%2\32;%3ENC_END: e15:e16:e17:0,%1 ENC_END:-%2
#define e15:e16:e17:0,%1+%2\32;%3ENC_END: e16:e17:0,%1 ENC_END:+%2
#define e16:e17:0,%1=%2\32;%3ENC_END: e17:0,%1 ENC_END:=%2
#define e17:0,%1,%2\32;%3ENC_END: 0,%1 ENC_END:,%2

// Strip trailing comma
#define MODEL_ARGS: \
_:@MA1:

#define @MA1:(%1),) \
%1)

// Swap [1][2] to [2][1]
#define ARRCHK:0,%1 @AR1:@AR2:@AR3:0,
#define ARRDELIM:
#define @AR1:@AR2:@AR3:0,%1. @AR3:0,%1.ARRDELIM:
#define @AR2:@AR3:0,%1ENC_END: @AR3:0,%1ENC_END:ARRDELIM:
#define @AR3:0,%1[%2]%3[%4]%5ARRDELIM: 0,%1[%4]%3[%2]%5ARRDELIM:

#define REFERENCE(%1.%2[%3]=>%4) \
@%4[%1_%2[%3]].

// ---------------------------------------------------------
// Player model
// ---------------------------------------------------------

// Basics
#define Player[%1]. ENC:@Player[%1].
#define @Player @Player1:@Player2:0,
#define @Player1:@Player2:0,[%1].%2.%3ENC_END: ARRCHK:Player_%2[%1].%3ENC_END:
#define @Player2:0,[%1].%2ENC_END: ARRCHK:0,Player_%2[%1]ENC_END:
// Functions
#define Player_addXp(%1)%2[%3] Player_addXp(MODEL_ARGS:(%3),%1)%2
// References
#define Player_gang[%1]. REFERENCE(Player.gang[%1]=>Gang)
// Variables
new Player_gang[MAX_PLAYERS] = {-1, ...};
new Player_xp[MAX_PLAYERS];
new Player_level[MAX_PLAYERS];
// Functions
stock Player_addXp(p, xp) {
Player[p].xp += xp;

// Every 100 xp is a new level
while (Player[p].xp > 100) {
Player[p].xp -= 100;
Player[p].level++;
}
}

// ---------------------------------------------------------
// Gang model
// ---------------------------------------------------------

// Basics
#define Gang[%1]. ENC:@Gang[%1].
#define @Gang @Gang1:@Gang2:0,
#define @Gang1:@Gang2:0,[%1].%2.%3ENC_END: ARRCHK:Gang_%2[%1].%3ENC_END:
#define @Gang2:0,[%1].%2ENC_END: ARRCHK:0,Gang_%2[%1]ENC_END:

// Functions
#define Gang_updateMembers(%1)%2[%3] Gang_updateMembers(MODEL_ARGS:(%3),%1)%2

// References
#define Gang_members%1.%2ENC_END: @GM1:@GM2:@GM3:0,Gang_members%1.%2ENC_END:
#define @GM1:@GM2:@GM3:0,Gang_members[%1][%2]. @Player[Gang_members[%2][%1]].
#define @GM2:@GM3:0,Gang_members[%1].%2ENC_END: Gang_members_%2[%1]ENC_END:
#define Gang_members_loop(%1)%2[%3]%4ENC_END: \
0,DM@d=DM@d+0); \
Gang[%3].updateMembers(); \
for(new __i = 0, %1;__i<sizeof(Gang_members[]);__i++) \
if((%1 = Gang_members[%3][__i])==-1){continue;}else
stock DM@d;

// Variables
#define MAX_GANGS 20
new Gang_members[MAX_GANGS][MAX_PLAYERS];
new Gang_name[MAX_GANGS][32];
new Gang_score[MAX_GANGS];

// Functions
stock Gang_updateMembers(g) {
new slot = 0;

for (new p = 0; p < MAX_PLAYERS; p++) {
if (Player[p].gang == g) {
Gang[g].members[slot++] = p;
}
}

// Fill the rest with -1
while (slot < MAX_PLAYERS) {
Gang[g].members[slot++] = -1;
}
}

// ---------------------------------------------------------
// Usage
// ---------------------------------------------------------

main() {
Gang[3].name = "the best gang";

Player[7].gang = 3;
Player[11].gang = 3;

Gang[3].members.loop(p) {
printf("player %d is in gang 3", p);

Player[p].addXp(20);
}


Player[11].gang.score += 13;

printf("Gang 3 has score: %d", Gang[3].score);

printf("player %d has %d xp",
Gang[3].members[1],
Gang[3].members[1].xp
);

printf(
"player 11 is in gang: %s",
Player[11].gang.name
);
}

I managed to nicely isolate the statements (using the ENC: macro). It can also simulate references between "objects".

Btw the macro resolver (slice-vps.nl/ppg-new) is up again.

Misiur
09/09/2013, 01:15 PM
Macro resolver rox! But does it simulate all of pawncc pitfalls? (like the %0\10; working but \10;%0 not and stuff)

#e: Oh, down again :(

Slice
09/09/2013, 01:33 PM
It uses a modified version of the Pawn compiler so it's 100% accurate. It's not down for me - could have been the very paranoid firewall. Try now.

Misiur
11/09/2013, 08:50 AM
Yup, it was firewall. The stepping option is awesome. The code is pure black magic for me even with the resolver though.

1. You are detecting character which is first after macro, and put a closing bracket before it

And this is all I can understand. The whole block with Arrcheck and stuff - what's its function?

Slice
11/09/2013, 09:00 AM
// Move the part between . and . or ENC_END in front of [1].
Player[1].something
Player_something[1]

Player[1].something.something
Player_something[1].something

Player[1].something[2]
Player_something[2][1]
// After ARRCHK:
Player_something[1][2]

Player[1].something[2].something
Player_something[2][1].something
// After ARRCHK:
Player_something[1][2].something