Galaxy does not support identifiers starting with _ (but yours accept it)
Your compiler messes up inlining sometimes, FixedToInt(InlinedFunction()), doesn't inline the function... however, it doesn't include the InlinedFunction() either... so the generated code references InlinedFunction(), but it doesn't exist.
Auto-completion does not work inside brackets... when you're indexing an array.
Ctrl-F does not select the content of the input-field... meaning, you often end up writing what you want to search for, behind what you previously searched for.
It doesn't complain if you define them, and it will rename them to have unique names. But if you try to invoke one of them, you get an error about ambiguous invokes.
Ah, that might explain it, I always messed up when copying my trigger functions, which of course aren't "invoked" as usual.
Auto-indentation messes up when {} or }{ exist on a single line... most troublesome is "} else {"
Integers cannot be typecast to bytes, or converted at all right now it seems, bytes can't be typecast either it seems. This is quite an issue right now.
Page down, goes to the end of the document, not a page down.
Something is off with "replace all", was going to replace 2 items, it said 2 items where replaced I think, but only 1 item was actually replaced it seemed, pressing again said 0 items was replaced ... but yet the last one also got replaced.
Doesn't seem to complain when there are several identical function signatures.
"cond ? iftrue : iffalse", would be much appreciated ... although the expansion might be less than trivial I assume... would simplify certain code, but far from important really.
Well.. in any case, in order for the galaxy compiler to accept it, the fields must be defined before they are used. And they were marked as const (before the function implementation). Still, some of them appeared to not be set when using them in TriggerCreate outside a method. I don't understand why the errors are there, but they are.. so I'll try rewriting stuff until it works.
I think I'll keep the obfuscation the way it is. While an adversary could just use the algorithm to decrypt strings, it does take quite a bit of work on his part.. Assuming short naming is also on in the map, he would have a hard time finding out which string is used in what context, and he would need to take time to write a map/program to decrypt all the strings. Yes, it's still possible, but that's the point of obfuscation. I don't want obfuscation to just keep the strings in cleartext, out of context.
Well, not really. All a user would have to do is to just insert a TriggerDebugOutput in your deobfuscation-function, and voila, all strings that are encountered can be seen as "from->to" in the debugger, and can be easily replaced in the script afterwards. This way he'd basically get access to all the vital strings... and even just extracting all the strings from the script-file and running them through your deobfuscator the same way would require minimal effort.
Meaning, I see little point in using an advanced encryption function, when a very basic one would work just as well (read: non-trivial). Hiding the actual strings is an obstacle, but it doesn't matter how high the obstacle is if you can just go around it.
I'm not saying that obfuscation doesn't work, it does, but the way yours is implemented severely restricts the usefulness, it doesn't matter how advanced of an encryption you use, I would still just use the above method, and it would work every time, minimal effort. If you were to inline the function-calls and parts of the deobfuscation-function and provide a separate seed for every string, then yeah, it would be a whole lot harder (however, care still needs to taken when caching the strings).
I'm guessing one might even be able to play with arrays as well for the decryption, splitting the encrypted strings (compilation), store them in arrays (runtime) and then assemble them back together with tricky indexing math... as long as there is no single deobfuscation function after this, it mighty be really rather annoying. However, the issue is always, caching the results or even just storing it in intermediate strings would make it easy to intercept.
The idea is to remove any single vulnerable point, so that it can't be trivially automated or intercepted. Lots of manual labor is probably the biggest turn-off.
would not be deobfuscated correctly. Don't know why, but using a function call for getting the value seemed to work better. It's not perfect though.. There are still some errors like the above which persists. I'm thinking I could try putting fields like that into the start of the MapInit function, but this is really a frustrating and illogical issue :(
Ah ok, anyway, I'd say that you could probably simplify your deobfuscation function, it doesn't matter what kind of advanced stuff it does, it is all visible in the debugger and can easily be deobfuscated using the function itself. Implementing something minimal would be good enough I'd say (just some form generated scrambled look-up table and doing the lookup with some variable offset, or such should be more than good enough or not make a difference anyway).
But then again, since it's only done once, perhaps it doesn't really matter, I'm not sure how much of an impact it actually has right now (probably not significant).
Although I'm not exactly sure I follow what's going wrong in your example, as long as have "string x = deobfuscate("blablabla")" BEFORE anything that uses it, it should be fine right? Or is the order of initialization not guaranteed for variables perhaps?
I'm curious why string obfuscation uses nested-methods instead of just using global strings that are decoded on init? Are there any obfuscation benefit to doing that?
EDIT: Also, perhaps you should strip out all indentation and empty lines when obfuscating.
int[42]MyStruct_a;bool[42]MyStruct_b;byte[42]MyStruct_FreeIndexes;int[2]MyStruct_InUse;//I assume that a bool takes up 1 byte. Want to save memory here.intMyStruct_FreeSlotsLeft=42;//Array indexes must be integers//Run this as first thing during map initvoidMyStruct_PreProcess(){inti=0;while(i<42){MyStruct_FreeIndexes[i]=i;i=i+1;}}intPower2(inti){//Did try doing this with >>, but 1>>1 returned 0 :Sintret=1;while(i>0){ret*=2;i-=1;}returnret;}byteMyStruct_Create(){bytei;if(MyStruct_FreeSlotsLeft==0){UIDisplayMessage(PlayerGroupAll(),c_messageAreaDebug,StringToText("Attempeted to allocate MyStruct, but the defined maximum had already been reached"));IntToString(1/0);}i=MyStruct_FreeIndexes[0];MyStruct_FreeSlotsLeft-=1;MyStruct_FreeIndexes[0]=MyStruct_FreeIndexes[MyStruct_FreeSlotsLeft];MyStruct_InUse[i/31]=MyStruct_InUse[i/31]|Power2(i%31);returni;}intMyStruct_Delete(inti){//Check if the specified index was already deletedif(MyStruct_InUse[i/31]Power2(i%31)==0){return;}MyStruct_InUse[i/31]-=Power2(i%31);MyStruct_FreeIndexes[MyStruct_FreeSlotsLeft]=i;MyStruct_FreeSlotsLeft+=1;}
The previous algorithm would take O(n) time for creating, O(1) for deleting, and 4*log31(n) extra bytes of memory for keeping track of free indexes (where n is the size of the arrays, and I assume I make the same optimization, for saving bool arrays, to the previous algorithm as done above).
This one would take O(1) time for creating and deleting, but 4*(n + 1) + 4*log31(n) extra bytes of memory (for n>255). n + 4 + 4*log31(n) if n<256.
Actually, since the Power2 function isn't exactly O(1), but O(i), and i is bounded by n, the algorithm will still run O(n). So, the second algorithm would allocate a lot more memory, and run O(n) too, because I now need to check that the user doesn't delete the same object twice.
Also, I really don't think that the first one would hit a running time of n very often in practice. Especially not if we add the optimization you mentioned about decrementing MyStruct_Index when deleting (if the deleted object was the last allocated object).
Can you see an optimization that would improve this algorithm, or did you have something else in mind?
I made the assumption that if the user deletes an item twice, it's his fault (meaning, create O(1), delete O(1), create pops, delete pushes). But yeah, that would definately mess things up badly if it happened, in very weird ways. However, one could just add the queue as an optimization on-top of your algorithm... it would make create, delete O(1) and really fast, and safe, however, it would add the 1-bit overhead PLUS 1-16 bits for the entries in the queue.
And I do agree that for the kind of algorithms and structures we see in SC2, I find it hard to imagine that it would spend O(n) looking for an available instance very often like you say (and increasing the pool size only even slightly would make it a non-issue). And I doubt it would be an issue even if it frequently did do a full scan (create/delete shouldn't be happening in such huge amounts). I doubt the performance gains of my proposed algorithm above is worthwhile, however, what may make it worthwhile is that it is predictable, create and delete will always be strictly O(1), which may be important for really large pools. Again though, I'm doubtful that it is an issue to begin with, but that's the only reason I see for considering it.
I would argue that you can do away storing "used" as bits in an int, as the overhead would be minimal for any reasonable uses... although at the same time, I doubt the mathematical overhead is even noticeable (again, construct/destruct should be somewhat rare) so the memory savings may be worth it. However, Power2() shouldn't be O(i) unless it's badly implemented, it really should be a simple lookup-function (oh hey, perhaps it's faster just to have your own lookup), or at the very least, be recursive O(log(i)).
-
Btw, I have no code to show you, but it seems that #inline messes up at times, I had a very simple function that just returned a value from an array. However, when inlined, would cause script errors on launch. Also, inlining produced really horrible code ;) ... it even seems to be assigning the variable to itself (inlinedvar = inlinedvar).
Found a compile bug (or so I argue anyway), your compiler tends to re-use variables when it can, which is ok. But it doesn't reset their values when it reuses them. Which is rather dangerous in my opinion, since variables always start with a default value, I would say that reused variables should be reset.
Making something run every frame is bound to be inefficient tbh. Only way to do something every frame that I can think of is doing a periodic event every 0 seconds. That actually gets run more often than the frame redraw.. In my test, i made an increment of a global variable every 0 seconds. The trigger ran 23 times pr second and caused the fps (ctrl, alt, f) to drop to 4.
Anyway, it's still a problem that the data table might cause lag spikes.
About the implementation. I would like to keep things as simple as possible for the user. So I would probably rather have some way of setting a struct dynamic types to be global variables, and then keep everything else the way it is.
Such as
#array[42] struct MyStruct{inta;boolb;voidSetA(inti){a=i;}}voidfoo(){MyStructstr;str.SetA(2);}BecomesstructMyStruct{inta;boolb;}int[42]MyStruct_a;bool[42]MyStruct_b;bool[42]MyStruct_Used;intMyStruct_Index;intMyStruct_Create(){inti=MyStruct_Index+1;while(true){if(i>=42){i=0;}if(!MyStruct_Used[i]){break;}if(i==MyStruct_Index){UIDisplayMessage(PlayerGroupAll(),c_messageAreaDebug,StringToText("Attempeted to allocate MyStruct, but the defined maximum had already been reached"));IntToString(1/0);}i=i+1;}MyStruct_Index=i;MyStruct_Used[i]=true;returni;}//Could be inlinedintMyStruct_Delete(inti){MyStruct_Used[i]=false;}voidMyStruct_SetA(inti,intstructIndex){MyStructcurrentStruct;currentStruct.a=MyStruct_a[structIndex];currentStruct.a=i;MyStruct_a[structIndex]=currentStruct.a;//Yes, yes.. In this case, I might just do MyStruct_a[structIndex] = i; but that is an unrelated optimization.}voidfoo(){MyStructstr;//Create a dynamic version of the struct, and pass that to the methodintbulkCopyVar=MyStruct_Create();MyStruct_a[bulkCopyVar]=str.a;MyStruct_SetA(2,bulkCopyVar);str.a=MyStruct_a[bulkCopyVar];MyStruct_Delete(bulkCopyVar);}
A solution like that would not require the user to worry about anything but the #array[42] notation.
It apears to be working for me.. Unless I misunderstand your problem. Could you be more specific about what actually prevents you from making changes in the bottom of the file?
-
I fixed some bugs and such.. v2.1.7
Fixed a bug that made compile and save act as compile and save as
Fixed a bug where a project's properties could not be changed if the project directory had changed.
The editor now accepts + and - for points, like galaxy does.
Added typedefs. (typedef int* pInt;)
Fixed an error that made trigger definitions conflict incorrectly with some methods.
Yeah definately, stuff shouldn't be run every frame, but exceeding the frame time in execution time is bound to have bad results, as you say. And galaxy doesn't appear to be all that fast.
Looks good to me, although I can think of two things right now...
MyStruct_Delete() should probably decrement MyStruct_Index until it finds a used index, but only if the index being deleted is the same as MyStruct_Index. This would make short-lived objects not impact the overall performance as much for larger well-populated pools (although note, it would have to count the number of allocated objects too). But it depends on what pool-type you decide for, and how it will be used.
Speaking of which, one could probably implement a strictly O(1) approach to this, by keeping a list of bytes/integers instead of a bool, and having that be a queue of available object indexes (freed indexes are appended to the end). A byte would accomodate a pool up to 256 objects, which should be good enough for most circumstances. One could then fall back to integers or bools (integers could be cheaply split into 2 byte-integers instead for a maximum of 65k objects, if memory is an issue). Anyway, just a though, I'm not really sure about all the different uses of pools for SC2 right now, so it might really be a non-issue to start with.
As for the transformation of foo(), I would advice against being able to instantiate the struct locally, as it could then not be passed around without bulkcopying, like you do in foo(), which interestingly, is rather pointless it seems because you still have to allocate an object from the pool. And if you intend to support constructors/destructors (which should trivial), then you always have to allocate it from the pool.
-
Regarding whimsyduke, I do believe he's specifically referring to chinese characters, not regular ascii.
What do you mean by iterations? Iterations of what?
My main reason for using the data table is that objects that are not allocated will not take up memory, but if performance is really an issue for people, I can make an option for using arrays instead. I would probably need the user to specify the size of these arrays, since it's impossible to make an algorithm that gives an upper bound of how many objects will be created during computation.
It is no small task though, so I probably wont look into it before my next exam :)
First off, I should note that galaxy-structs should be avoided for something like this, they consume more memory than the sum of their properties. Most likely because they act as references. So, inlining the properties and prefixing them would be beneficial.
1.000 iterations of doing 5 read/writes ;) Aka, if it took 5 read/writes to "update unit data", then you could "update unit data" 1.000 times every frame... with a big if; being that's all the function does... it's very likely to do a lot of other stuff as well, so the real number would be lower. This is mostly an issue during intensive triggers, say, when creating the map, spawning waves, checking win conditions, etc, etc.
Anyway, yeah, fixed pool-size is obviously a must because of how galaxy works. However, you really don't have to implement the hard stuff I wrote there, I'm not even sure all that is necessary.
My point is, if you only simply implement the syntactic sugar, say something like this (just as an example, I haven't thought too deeply about the syntax):
Then that would provide the foundation, allocation/deallocation, etc could be built by the user on-top of that as allocators. And even your allocators could be provided on-top of that (worthless example syntax, but just as an illustration):
pool alloc g_mypool
pool refgc g_mypool
pool basic g_mypool
pool index g_mypool
EDIT: stupid wikicreole not working as it should it seems, there should be a # in-front of each of the lines, and you're meant to use ONE of them.
That would then create an allocator of the specified type bound to the pool, exposing the necessary functions as well (lbasically copy-paste functions). The only one that would really require special attention would be "refgc" if it would "make the cut". Basically, it would require that you monitor the construction and destruction of pool pointers (but even that shouldn't be overly complicated unless I'm mistaken).
As an example, [alloc] would expose "new(...)" and "delete(p)", [refgc] would expose "new(...)", [basic] would expose "new(...)" and "delete(p)" (calls destructor), [index] would expose "get(i)" and "index(p)" (as well as perhaps "construct(i/p)" and "destruct(i/p)", or something like that). Although I'm not sure how useful [index] really is if the others are available, the idea would be to be able to design your own allocator... but not sure if there are good reasons for actually doing that in SC2.
So again, I'd just like to make it clear that I'm not aggressively pushing for this to be implement, simply trying to discuss its usefulness and possibilities.
EDIT: It should be noted that these would actually simulate classes, they would support constructors and optionally destructors.
It has been a long time since pointers were avalible in galaxy. On the site you linked there is a comment from Sun, 20 Jun 2010 saying Quote:
Pointers were taken out completely in patch 9. Way to go, Blizzard.
So there are no * or in regular galaxy. If you would want to do something like
MyStruct*foo(){MyStructstr;returnstr;}
Then my only option is to create a new dynamic type(in the data table), and copy the struct there. I have two issues with that. First, any changes to that dynamic type would not change the variable from the method, and second, it is not apparent that a new variable is created, and thus need to be deleted aswell.
I thought a bit and did a few benchmarks.
Setting a boolean 100.000 times using DataTables, using a one char string suffixed with an integer index... 360ms (slower for larger strings)
Getting a boolean 100.000 times using DataTables, using a one char string suffixed with an integer index... 320ms (slower for larger strings)
Setting a boolean 100.000 times using a global bool, using an integer index... 20ms
Getting a boolean 100.000 times using a global bool, using an integer index... 10ms
Crunching the numbers, first off, using DataTable is more than 18x slower for writing, and 36x slower for reading (note, I have not compensated for the for-loop overhead).
Now, let's assume that every other operation to the DataTable is get/set, that would yield an average of 340ms per 100.000 operations, or 300.000 per second. May seem like a big number... so let's see how many operations that would be for a normal framerate, 300.000/60 = 5.000 operations per frame.
That is not alot considering that there may be a lot of data per struct, as well as reading and writing! Doing a total of 5 operations would yield 1.000 iterations per frame... just for getting and setting the data, NOTHING else. Now of course, we're not doing everything per frame, but exceeding the frame time too much will noticeably affect gameplay.
--
The sad truth about globals is though that Blizzard decided that in the age of wide-spread use of 2GB memory, that 2MB is enough. But it's not really as bad as it sounds, that would equate to 500.000 integers, say, 400.000 to give room for the scripts and everything else.
Native types are 4 bytes, types that are objects consume additional memory depending on their type, but can also share this additional memory if referencing the same data. So, let's say we have a struct with 40 bytes worth of data (that is a lot), that would give us 10.000 objects to work with... a lot more than is needed for most uses. SC2 usually buckles under the pressure of a 1.000 fighting units.
So what I'm proposing is to be able to create struct-pools (user-defined size), each variable in a struct would expand to separate global prefixed variables, each instance in the pool would also be subject to a 1 bool/byte overhead (whether it is allocated or free, or possibly even a handle-count, could also be removed for pools without deallocation). Instead of passing around a struct, an integer is passed around as a makeshift pointer into a pool. All attempts to access a property in a struct would be substituted as "object.property" => "global_struct_pool_property[object_id]".
A pool could work in 3 ways:
Allocation and deallocation is handled entirely by the user (1 byte overhead), can be hard to manage, but could work sufficiently for large-scale but inherently simple housekeeping (which most is in SC2).
Acquire and release increment and decrement a per index-counter (1-4 byte overhead), basic garbage collection, when a struct reference is set, increment, when the reference is reset or leaves the scope, decrement.
No deallocation (faster, no overhead), useful for things like keeping track of players, where an index is never reused, although actual usefulness is debatable.
"Not-really-a-pool", to simply be able to benefit from having struct-integer-ids resolve to global variable arrays, "object.property" => "global_struct_pool_property[object_id]", and let the user deal with allocaton and such. This would be a nice and likely rather simple addition.
EDIT: Crap, posted before I was finished :P
--
I merely propose the implementation of this, if it is wortwhile is up to you. It could be manually implemented right now, but inclusion into the editor would hugely simplify accessing the properties of a struct (and functions inside a struct). Do note, if large amounts of data is intended to be stored in a struct, one could manually use DataTable to do so (perhaps a handy id-helper could be made available, also note that constructors/destructors are available), or it could potentially even be possible to be able to specify specific properties as "DataTable properties".
So, I'm a programmer myself obviously so I'm aware of the work that might go into something like this, I'm merely suggesting it as an improvement.
It has been a long time since pointers were avalible in galaxy. On the site you linked there is a comment from Sun, 20 Jun 2010 saying Quote:
Pointers were taken out completely in patch 9. Way to go, Blizzard.
So there are no * or in regular galaxy. If you would want to do something like
MyStruct*foo(){MyStructstr;returnstr;}
Then my only option is to create a new dynamic type(in the data table), and copy the struct there. I have two issues with that. First, any changes to that dynamic type would not change the variable from the method, and second, it is not apparent that a new variable is created, and thus need to be deleted aswell.
Yay Blizzard.
So, I take it structs now have no actual purpose other than being able to bunch variables together? Or have I missed something vital? They can't be copied, can't be referenced, can't be used as an argument, can't be dynamically allocated.
EDIT: Ok, they can be copied... but I'm not sure what that's actually good for though, other than for creating new immutable types?
Not sure if you are referring specfically to "files as map", or the option itself... I personally use it to compile the scripts back into my source-map folder, so that I can then test from the sc2editor without having to manually copy the output files.
This would only be an advantage, if you could inject the script, while the map is already open in the SC2 editor, because you could test the map instantly. But thats not possible, it seems, so there is no difference between selecting a map to inject code and creating a new one, at least for map files.
For component lists, this is possible, it seems, so makes sense for folders.
BTW why do you need to specify an output file every time you save (aka press okay another time)? Can't you just use the already saved one?
And please add a "Don't show this again" checkbox for the "Do you want to start the map now" message.
Yeah, componentlists was per my request, for sc2maps I'm not sure what the point would be.
Specifying a new output file every time you save seems to be a bug, if you select an output-folder, and restart galaxy, and use "compile and save", it stops asking, but if you ever touch "compile and save as", it won't stop asking until you restart it again.
I looked at how you implement functions in structs (as well as classes) and realized that you use DataTables for that, it seems rather inefficient and pointless, but perhaps I missed something. Point being that, you can pass structures by reference in galaxy, as well as of course creating references to structures... however, it is not possible to create references(&) to structures in your editor (http://www.sc2mapster.com/api-docs/galaxy-language/structs/).
I would expect functions defined in structs to simply be put outside the struct and be reasonably prefixed, and then use the struct reference as the first parameter/argument [e.g.: void test(mystruct* this, int arg1) {...}].
Also, it would be very nice if creating references (&) was made possible too as it is in regular galaxy.
Whats the point of selecting an output map? Shouldn't it just create an output map with a specified name? Why would you need to select a file instead of a name?
Not sure if you are referring specfically to "files as map", or the option itself... I personally use it to compile the scripts back into my source-map folder, so that I can then test from the sc2editor without having to manually copy the output files.
Found some compiler bugs:
A few other things I've noticed:
Awesome!
Ah, that might explain it, I always messed up when copying my trigger functions, which of course aren't "invoked" as usual.
Long time no see I guess ;)
Anyway, found a few more issues:
Well, not really. All a user would have to do is to just insert a TriggerDebugOutput in your deobfuscation-function, and voila, all strings that are encountered can be seen as "from->to" in the debugger, and can be easily replaced in the script afterwards. This way he'd basically get access to all the vital strings... and even just extracting all the strings from the script-file and running them through your deobfuscator the same way would require minimal effort.
Meaning, I see little point in using an advanced encryption function, when a very basic one would work just as well (read: non-trivial). Hiding the actual strings is an obstacle, but it doesn't matter how high the obstacle is if you can just go around it.
I'm not saying that obfuscation doesn't work, it does, but the way yours is implemented severely restricts the usefulness, it doesn't matter how advanced of an encryption you use, I would still just use the above method, and it would work every time, minimal effort. If you were to inline the function-calls and parts of the deobfuscation-function and provide a separate seed for every string, then yeah, it would be a whole lot harder (however, care still needs to taken when caching the strings).
I'm guessing one might even be able to play with arrays as well for the decryption, splitting the encrypted strings (compilation), store them in arrays (runtime) and then assemble them back together with tricky indexing math... as long as there is no single deobfuscation function after this, it mighty be really rather annoying. However, the issue is always, caching the results or even just storing it in intermediate strings would make it easy to intercept.
The idea is to remove any single vulnerable point, so that it can't be trivially automated or intercepted. Lots of manual labor is probably the biggest turn-off.
Ah ok, anyway, I'd say that you could probably simplify your deobfuscation function, it doesn't matter what kind of advanced stuff it does, it is all visible in the debugger and can easily be deobfuscated using the function itself. Implementing something minimal would be good enough I'd say (just some form generated scrambled look-up table and doing the lookup with some variable offset, or such should be more than good enough or not make a difference anyway).
But then again, since it's only done once, perhaps it doesn't really matter, I'm not sure how much of an impact it actually has right now (probably not significant).
Although I'm not exactly sure I follow what's going wrong in your example, as long as have "string x = deobfuscate("blablabla")" BEFORE anything that uses it, it should be fine right? Or is the order of initialization not guaranteed for variables perhaps?
I'm curious why string obfuscation uses nested-methods instead of just using global strings that are decoded on init? Are there any obfuscation benefit to doing that?
EDIT: Also, perhaps you should strip out all indentation and empty lines when obfuscating.
Nevermind
I made the assumption that if the user deletes an item twice, it's his fault (meaning, create O(1), delete O(1), create pops, delete pushes). But yeah, that would definately mess things up badly if it happened, in very weird ways. However, one could just add the queue as an optimization on-top of your algorithm... it would make create, delete O(1) and really fast, and safe, however, it would add the 1-bit overhead PLUS 1-16 bits for the entries in the queue.
And I do agree that for the kind of algorithms and structures we see in SC2, I find it hard to imagine that it would spend O(n) looking for an available instance very often like you say (and increasing the pool size only even slightly would make it a non-issue). And I doubt it would be an issue even if it frequently did do a full scan (create/delete shouldn't be happening in such huge amounts). I doubt the performance gains of my proposed algorithm above is worthwhile, however, what may make it worthwhile is that it is predictable, create and delete will always be strictly O(1), which may be important for really large pools. Again though, I'm doubtful that it is an issue to begin with, but that's the only reason I see for considering it.
I would argue that you can do away storing "used" as bits in an int, as the overhead would be minimal for any reasonable uses... although at the same time, I doubt the mathematical overhead is even noticeable (again, construct/destruct should be somewhat rare) so the memory savings may be worth it. However, Power2() shouldn't be O(i) unless it's badly implemented, it really should be a simple lookup-function (oh hey, perhaps it's faster just to have your own lookup), or at the very least, be recursive O(log(i)).
-Btw, I have no code to show you, but it seems that #inline messes up at times, I had a very simple function that just returned a value from an array. However, when inlined, would cause script errors on launch. Also, inlining produced really horrible code ;) ... it even seems to be assigning the variable to itself (inlinedvar = inlinedvar).
Found a compile bug (or so I argue anyway), your compiler tends to re-use variables when it can, which is ok. But it doesn't reset their values when it reuses them. Which is rather dangerous in my opinion, since variables always start with a default value, I would say that reused variables should be reset.
Yeah definately, stuff shouldn't be run every frame, but exceeding the frame time in execution time is bound to have bad results, as you say. And galaxy doesn't appear to be all that fast.
Looks good to me, although I can think of two things right now...
MyStruct_Delete() should probably decrement MyStruct_Index until it finds a used index, but only if the index being deleted is the same as MyStruct_Index. This would make short-lived objects not impact the overall performance as much for larger well-populated pools (although note, it would have to count the number of allocated objects too). But it depends on what pool-type you decide for, and how it will be used.
Speaking of which, one could probably implement a strictly O(1) approach to this, by keeping a list of bytes/integers instead of a bool, and having that be a queue of available object indexes (freed indexes are appended to the end). A byte would accomodate a pool up to 256 objects, which should be good enough for most circumstances. One could then fall back to integers or bools (integers could be cheaply split into 2 byte-integers instead for a maximum of 65k objects, if memory is an issue). Anyway, just a though, I'm not really sure about all the different uses of pools for SC2 right now, so it might really be a non-issue to start with.
As for the transformation of foo(), I would advice against being able to instantiate the struct locally, as it could then not be passed around without bulkcopying, like you do in foo(), which interestingly, is rather pointless it seems because you still have to allocate an object from the pool. And if you intend to support constructors/destructors (which should trivial), then you always have to allocate it from the pool.
-Regarding whimsyduke, I do believe he's specifically referring to chinese characters, not regular ascii.
First off, I should note that galaxy-structs should be avoided for something like this, they consume more memory than the sum of their properties. Most likely because they act as references. So, inlining the properties and prefixing them would be beneficial.
1.000 iterations of doing 5 read/writes ;) Aka, if it took 5 read/writes to "update unit data", then you could "update unit data" 1.000 times every frame... with a big if; being that's all the function does... it's very likely to do a lot of other stuff as well, so the real number would be lower. This is mostly an issue during intensive triggers, say, when creating the map, spawning waves, checking win conditions, etc, etc.
Anyway, yeah, fixed pool-size is obviously a must because of how galaxy works. However, you really don't have to implement the hard stuff I wrote there, I'm not even sure all that is necessary.
My point is, if you only simply implement the syntactic sugar, say something like this (just as an example, I haven't thought too deeply about the syntax):
would translate into
Then that would provide the foundation, allocation/deallocation, etc could be built by the user on-top of that as allocators. And even your allocators could be provided on-top of that (worthless example syntax, but just as an illustration):
pool alloc g_mypool
pool refgc g_mypool
pool basic g_mypool
pool index g_mypool
EDIT: stupid wikicreole not working as it should it seems, there should be a # in-front of each of the lines, and you're meant to use ONE of them.
That would then create an allocator of the specified type bound to the pool, exposing the necessary functions as well (lbasically copy-paste functions). The only one that would really require special attention would be "refgc" if it would "make the cut". Basically, it would require that you monitor the construction and destruction of pool pointers (but even that shouldn't be overly complicated unless I'm mistaken).
As an example, [alloc] would expose "new(...)" and "delete(p)", [refgc] would expose "new(...)", [basic] would expose "new(...)" and "delete(p)" (calls destructor), [index] would expose "get(i)" and "index(p)" (as well as perhaps "construct(i/p)" and "destruct(i/p)", or something like that). Although I'm not sure how useful [index] really is if the others are available, the idea would be to be able to design your own allocator... but not sure if there are good reasons for actually doing that in SC2.
So again, I'd just like to make it clear that I'm not aggressively pushing for this to be implement, simply trying to discuss its usefulness and possibilities.
EDIT: It should be noted that these would actually simulate classes, they would support constructors and optionally destructors.
PS. Good luck with the exam.
I thought a bit and did a few benchmarks.
Crunching the numbers, first off, using DataTable is more than 18x slower for writing, and 36x slower for reading (note, I have not compensated for the for-loop overhead).
Now, let's assume that every other operation to the DataTable is get/set, that would yield an average of 340ms per 100.000 operations, or 300.000 per second. May seem like a big number... so let's see how many operations that would be for a normal framerate, 300.000/60 = 5.000 operations per frame.
That is not alot considering that there may be a lot of data per struct, as well as reading and writing! Doing a total of 5 operations would yield 1.000 iterations per frame... just for getting and setting the data, NOTHING else. Now of course, we're not doing everything per frame, but exceeding the frame time too much will noticeably affect gameplay.
--
The sad truth about globals is though that Blizzard decided that in the age of wide-spread use of 2GB memory, that 2MB is enough. But it's not really as bad as it sounds, that would equate to 500.000 integers, say, 400.000 to give room for the scripts and everything else.
Native types are 4 bytes, types that are objects consume additional memory depending on their type, but can also share this additional memory if referencing the same data. So, let's say we have a struct with 40 bytes worth of data (that is a lot), that would give us 10.000 objects to work with... a lot more than is needed for most uses. SC2 usually buckles under the pressure of a 1.000 fighting units.
So what I'm proposing is to be able to create struct-pools (user-defined size), each variable in a struct would expand to separate global prefixed variables, each instance in the pool would also be subject to a 1 bool/byte overhead (whether it is allocated or free, or possibly even a handle-count, could also be removed for pools without deallocation). Instead of passing around a struct, an integer is passed around as a makeshift pointer into a pool. All attempts to access a property in a struct would be substituted as "object.property" => "global_struct_pool_property[object_id]".
A pool could work in 3 ways:
EDIT: Crap, posted before I was finished :P
--
I merely propose the implementation of this, if it is wortwhile is up to you. It could be manually implemented right now, but inclusion into the editor would hugely simplify accessing the properties of a struct (and functions inside a struct). Do note, if large amounts of data is intended to be stored in a struct, one could manually use DataTable to do so (perhaps a handy id-helper could be made available, also note that constructors/destructors are available), or it could potentially even be possible to be able to specify specific properties as "DataTable properties".
So, I'm a programmer myself obviously so I'm aware of the work that might go into something like this, I'm merely suggesting it as an improvement.
Yay Blizzard.
So, I take it structs now have no actual purpose other than being able to bunch variables together? Or have I missed something vital? They can't be copied, can't be referenced, can't be used as an argument, can't be dynamically allocated.
EDIT: Ok, they can be copied... but I'm not sure what that's actually good for though, other than for creating new immutable types?
Yeah, componentlists was per my request, for sc2maps I'm not sure what the point would be.
Specifying a new output file every time you save seems to be a bug, if you select an output-folder, and restart galaxy, and use "compile and save", it stops asking, but if you ever touch "compile and save as", it won't stop asking until you restart it again.
I looked at how you implement functions in structs (as well as classes) and realized that you use DataTables for that, it seems rather inefficient and pointless, but perhaps I missed something. Point being that, you can pass structures by reference in galaxy, as well as of course creating references to structures... however, it is not possible to create references(&) to structures in your editor (http://www.sc2mapster.com/api-docs/galaxy-language/structs/).
I would expect functions defined in structs to simply be put outside the struct and be reasonably prefixed, and then use the struct reference as the first parameter/argument [e.g.: void test(mystruct* this, int arg1) {...}].
Also, it would be very nice if creating references (&) was made possible too as it is in regular galaxy.
Not sure if you are referring specfically to "files as map", or the option itself... I personally use it to compile the scripts back into my source-map folder, so that I can then test from the sc2editor without having to manually copy the output files.