This thread contains two small systems: UnitIndexer and UnitStructer (both of which use vJass).
UnitStructer creates a struct for each unit that enters the map and destroys the struct after the unit has been removed from the game. This struct is stored to the unit's UserData also known as Custom Value.
UnitIndexer does the same except that it 'creates' indexes instead of structs. The creation algorithm it uses is basically the same and produces the same integers as the one of structs, but is inlined and a bit faster. UnitIndexer is basically as light as any system that does the same can be.
The index or struct of a unit can be acquired with a GetUnitUserData call or in GUI with 'Unit - Custom Value of Unit'. The use of SetUnitUserData or 'Unit - Set Custom Value of Unit' screws up the system and as such is prohibited. Also you can't use RemoveUnit() or 'Unit - Remove' on living units. Instead you either have to use RemoveIndexerUnit() or 'Unit - Hide' and 'Unit - Explode'.
The structs created by the UnitStructer can be used just like any other structs are except that you must not destroy the ones that the system itself has assigned to units.
Both these structs as well as the indexes created by the UnitIndexer can be used to make things multi-unit instanceable the same way player numbers can be used to make things multi-player instanceable.
JASS Example:
set someArrayVariable[GetUnitUserData(u)] = data set data = someArrayVariable[GetUnitUserData(u)]
GUI Example:
Set someArrayVariable[(Custom value of (Last created unit))] = data Set data = someArrayVariable[(Custom value of (Last created unit))]
Take note that these indexes as well as structs are recycled when units die meaning that values stored to arrays using them as indexes aren't always initialized or nulled unless you do that yourself.
GUI Example:
NullUnitData
Events
Unit - A unit enters (Playable map area)
Conditions Actions
Set someArrayVariable[(Custom value of (Triggering unit))] = 0
Anything that uses the UnitIndexer works with the UnitStructer, but not always the other way around. Anything that uses Cohadar's PUI can be made to work with either of these by using this:
UnitIndexerToPUI
library PUI uses UnitIndexer // All you need to do when switching from PUI to UnitIndexer is to copy paste // the UnitIndexer library to your map, disable the previous PUI library that // you had in your map and replace RemoveUnit calls with RemoveIndexedUnit calls function GetUnitIndex takesunit u returnsinteger return GetUnitUserData(u) endfunction endlibrary
Last important note: neither one of these systems are active by the time structs' onInit methods are called. This means that units won't have indexes or structs by the time of their execution.
globals //---------------------------------------------------------------------- privateconstantreal TOTAL_DECAY_TIME = 90.00 // This should match the sum of the 'Decay Time (sec) - Bones' // and 'Decay Time (sec) - Flesh' gameplay constants. // The system will work with any value but is most optimized with // the correct value. //----------------------------------------------------------------------
//---------------------------------------------------------------------- privateconstantinteger UNIT_LIMIT = 4096 // If the amount of units in the map at once exceedes this limit // the UnitIndexer will malfunction. // Higher value increases the operations done in the systems init // function and might hit the op limit. //----------------------------------------------------------------------
//========================================================================== // If the native RemoveUnit is used on a living unit the removed unit's // index won't be recycled which is why this function must be used instead // in such cases. // function RemoveIndexedUnit takesunit u returnsnothing localinteger uind = GetUnitUserData(u) if IsAlive[uind]then set IsAlive[uind] = false set NextFreeIndex[uind] = FreeIndex set FreeIndex = uind endif call RemoveUnit(u) endfunction
//========================================================================== // This function is run whenever any unit enters the map. // privatefunction onEnter takesnothingreturnsboolean set IsAlive[FreeIndex] = true call SetUnitUserData(GetFilterUnit(), FreeIndex) set FreeIndex = NextFreeIndex[FreeIndex] returnfalse endfunction
//========================================================================== // This function is run whenever any unit dies. // privatefunction onDeath takesnothingreturnsnothing localunit u = GetTriggerUnit() localinteger uind = GetUnitUserData(u) if IsAlive[uind]then set IsAlive[uind] = false call TriggerSleepAction(3) loop //-------------------------------------------------------------- // Most units that decay exit this loop at the second iteration. // Those that don't decay exit right at the first iteration. // Heroes stay in the loop untill revived or removed. //-------------------------------------------------------------- exitwhen GetUnitUserData(u) == 0 if GetWidgetLife(u) >= 0.405 then set IsAlive[uind] = true set u = null return endif call TriggerSleepAction(TOTAL_DECAY_TIME) endloop set NextFreeIndex[uind] = FreeIndex set FreeIndex = uind endif set u = null endfunction
//************************************************************************** //* Editable Part * //**************************************************************************
globals privateconstantreal TOTAL_DECAY_TIME = 90.00 // This should match the sum of the 'Decay Time (sec) - Bones' // and 'Decay Time (sec) - Flesh' gameplay constants. // The system will work with any value but is most optimized with // the correct one. endglobals
struct UnitStruct
boolean someSystemFlag = true// don't touch this line
endstruct
privatefunction onEnter takesnothingreturnsboolean local UnitStruct uind = UnitStruct.create() localunit u = GetFilterUnit() call SetUnitUserData(u, integer(uind))
// I suggest that you don't edit the existing lines in this // function unless you know exactly what you're doing
set u = null returnfalse endfunction
//************************************************************************** //* End of Editable Part * //**************************************************************************
//========================================================================== // If the native RemoveUnit is used on a living unit, the unit's struct // won't be recycled. If there is a possibility that the unit is alive // use this instead of RemoveUnit(). // function RemoveIndexedUnit takesunit u returnsnothing local UnitStruct uind = UnitStruct(GetUnitUserData(u)) if uind.someSystemFlag then set uind.someSystemFlag = false call uind.destroy() endif call RemoveUnit(u) endfunction
//========================================================================== // This function is run whenever any unit dies. // privatefunction onDeath takesnothingreturnsnothing localunit u = GetTriggerUnit() local UnitStruct uind = UnitStruct(GetUnitUserData(u)) if uind.someSystemFlag then set uind.someSystemFlag = false call TriggerSleepAction(3) loop //-------------------------------------------------------------- // Most units that decay exit this loop at the second iteration. // Those that don't decay exit right at the first iteration. // Heroes stay in the loop untill revived or removed. //-------------------------------------------------------------- exitwhen GetUnitUserData(u) == 0 if GetWidgetLife(u) >= 0.405 then set uind.someSystemFlag = true set u = null return endif call TriggerSleepAction(TOTAL_DECAY_TIME) endloop call uind.destroy() endif set u = null endfunction
call TriggerAddAction(trig, function onDeath) call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_DEATH) endfunction
endlibrary// End of the UnitStructer library UnitIndexer uses UnitStructer // Anything that uses UnitIndexer works with UnitStructer endlibrary //==============================================================================
Changelog
v0.91
removed dynamic array feature
posted UnitStructer
v0.9
drastically improved the performance of the system
rewrote parts of the readme
Last edited by DiscipleOfLife; 04-14-2008 at 02:41 PM..
function s__normal__allocate takesnothingreturnsinteger localinteger this=si__normal_F if(this!=0)then set si__normal_F=si__normal_V[this] else set si__normal_I=si__normal_I+1 set this=si__normal_I endif set si__normal_V[this]=-1 return this endfunction
A structs destroy method:
function s__normal_destroy takesinteger this returnsnothing set si__normal_V[this]=si__normal_F set si__normal_F=this endfunction
UnitIndexer's create algorithm (using same parameter names):
localinteger this=si__normal_F set si__normal_F=si__normal_V[this]
UnitIndexer's destroy algorithm (using same parameter names):
set si__normal_V[this]=si__normal_F set si__normal_F=this
As you can see using structs would simply be a degradation. Not something I would call "much more powerful". If you like vJass syntax you can typecast the indexes to structs but in reality that won't improve the performance of your code.
About your idea of using structs without a system to store data to a unit's user data without interfering with other spells or systems that need to do the same: give an example please. I just don't believe it.
About your idea of using structs without a system to store data to a unit's user data without interfering with other spells or systems that need to do the same: give an example please. I just don't believe it.
Add more variables to the struct? This uses UserData anyways, so it would have to be custom coded correctly anyways. (As in, so that everything in the map used this instead of UserData directly)
Quote:
As you can see using structs would simply be a degradation. Not something I would call "much more powerful". If you like vJass syntax you can typecast the indexes to structs but in reality that won't improve the performance of your code.
Negligibly slower, but much cleaner and more powerful features are added to structs (Structs extending others, methods, and so on) - I am not suggesting that structs are any faster (I have not checked/tested for myself, but if you are correct about the allocation functions yours is at least faster for that), but that they are more powerful in terms of what they can do.
When do you create those structs? When do you destroy them? UnitIndexer does those two things for you and nothing more. UnitIndexer creates indexes when units enter the map and destroy them when the units no longer can hold user data. The main purpouse of the UnitIndexer is to save you from having to figure out if a unit already has a user 'struct' or when it should be destroyed and by doing so also reducing the amount of actions that need to be done when retrieving the data.
The only other solution I can think of is doing something similar to what Srilanc does in HAIL but with user data instead of H2I and without that useless 'property' wrapper thing. In other words having some kind of reference counter in the struct. When using such a system (or inlining the same by hand) you would have to manually release the indexes, which is ok in many situations but would rule out the use of a damage detection engine or any other similar system that needs units to have indexes from their very beginning to the end, which is exactly what the UnitIndexer provides. Also such a solution would require an if statement most of the time to check if the unit has a struct at all. UnitIndexer is simpler and faster.
Quote:
Originally Posted by PurplePoot
This uses UserData anyways, so it would have to be custom coded correctly anyways.
Yes "custom coded correctly" meaning that you dont set the user data to a struct. Instead you just use the index that already is stored in it (assuming ofc that you want to store more than 1 value to a unit).
Quote:
Originally Posted by PurplePoot
(As in, so that everything in the map used this instead of UserData directly)
Everything meaning the one system that could use UserData directly?
Quote:
Originally Posted by PurplePoot
Negligibly slower, but much cleaner and more powerful features are added to structs (Structs extending others, methods, and so on) - I am not suggesting that structs are any faster (I have not checked/tested for myself, but if you are correct about the allocation functions yours is at least faster for that), but that they are more powerful in terms of what they can do.
Cleaner? Did I already mentiont that you can typecast indexes to structs? I think did. Infact you already have to typecast unit user data to structs even if they were structs to begin with so I really don't see what the difference in syntax is. Can you spot a difference between these two?
User data holds a struct:
local Struct this = Struct(GetUnitUserData(u))
User data holds an index:
local Struct this = Struct(GetUnitUserData(u))
I honestly can't. Did I miss what you ment with 'much cleaner'? Did you mean something else than syntax? If you did then please elaborate because my imagination doesn't stretch that far.
As for the powerful features of structs: you can use most of them after a simple typecast. The only thing you can't do is extending. Though your propably the only one who has ever even imagined of needing to do so (when talking about a unit's 'struct'). You can go take a look at the PUI thread at wc3c which has been there for over 6 months and see for yourself if anyone has bothered Cohadar with this issue. Though the system could easily be modified to use real structs in a matter of minutes if one knows what he's doing (though I won't be doing that untill I have an actual reason to do so).
Yes "custom coded correctly" meaning that you dont set the user data to a struct. Instead you just use the index that already is stored in it (assuming ofc that you want to store more than 1 value to a unit).
What I meant is spells and systems etc that use the user data anyways would not do any better with this.
Quote:
When do you create those structs? When do you destroy them?
Making a trigger for that takes about five seconds.
Quote:
Everything meaning the one system that could use UserData directly?
Again, what I meant is it doesn't save you the trouble of having to make sure nothing else tried to do so (which is what it seemed to me like you were complaining about with structs earlier)
Quote:
Cleaner? Did I already mentiont that you can typecast indexes to structs? I think did. Infact you already have to typecast unit user data to structs even if they were structs to begin with so I really don't see what the difference in syntax is. Can you spot a difference between these two?
User data holds a struct:
local Struct this = Struct(GetUnitUserData(u))
User data holds an index:
local Struct this = Struct(GetUnitUserData(u))
I honestly can't. Did I miss what you ment with 'much cleaner'? Did you mean something else than syntax? If you did then please elaborate because my imagination doesn't stretch that far.
I see a difference; one way you don't have a system in your map -.-
Quote:
As for the powerful features of structs: you can use most of them after a simple typecast. The only thing you can't do is extending. Though your propably the only one who has ever even imagined of needing to do so (when talking about a unit's 'struct').
Example: You are making a medieval RTS map, you have Archers, Catapults, Cavalry, and Infantry. Cavalry get a boost in damage every time they hit an Archer, meaning Archers need an onDamage trigger and Cavalry need some other special data. Archers and Siege have a special attack type (basically, they attack where the unit is, but we don't want to have an Attack Ground option!), so that will require some stuff to, etc, etc. In a situation such as this, it is obviously more efficient to use a master struct and then child forms for each unit type, to avoid having useless things (like onDamage events on siege, cavalry, and infantry) that will slow the game down and not give any benefits.
Anyways, since we apparently can't agree on this, I'd like to get some other opinions on this (Earth-Fury/HINDY/wyrmlord/CG or someone if they want to/etc)
Type casting an arbitary number into a struct is a very bad idea. In order to make it properly work with structs, you'd need to have a linked list system to align structs and unit IDs properly. At this point you've got a lot of complications to make something that is in all likelyhood slower than attatching by GC.
If you really feel the need to use a system like this, you might want to use Handle Array Index Library by Strilanc, which fast, secure and doesn't use up the UnitUserData option.
__________________
Quote:
[20-00-13] MasterHaosis: Because I am retarded douchebag [20-15-23] MasterHaosis: I am faggot, retard and such.. [20-21-22] MasterHaosis: because I am chat moderator. And you dont have right to speak against me ~Void~: Knock it off, for fuck's sake. Get that bullshit out of your signature and stop being so immature. Let it die out.
What I meant is spells and systems etc that use the user data anyways would not do any better with this.
You are forgetting that with this you can have more than one of such systems or spells.
Quote:
Originally Posted by PurplePoot
Making a trigger for that takes about five seconds.
It took me a bit more than 5 seconds to code this thing. If it really takes so little effort for you to write a code that does the same then please write and post it. Im still mostly curious of when you create and destroy the structs, since you somehow failed to answer my question about that.
Quote:
Originally Posted by PurplePoot
Again, what I meant is it doesn't save you the trouble of having to make sure nothing else tried to do so (which is what it seemed to me like you were complaining about with structs earlier)
The system is designed to allow different spells and systems store to units effortlessly without knowing of or interfering with eachother. Isn't that already clear to you?
Quote:
Originally Posted by PurplePoot
Example: You are making a medieval RTS map, you have Archers, Catapults, Cavalry, and Infantry. Cavalry get a boost in damage every time they hit an Archer, meaning Archers need an onDamage trigger and Cavalry need some other special data. Archers and Siege have a special attack type (basically, they attack where the unit is, but we don't want to have an Attack Ground option!), so that will require some stuff to, etc, etc. In a situation such as this, it is obviously more efficient to use a master struct and then child forms for each unit type, to avoid having useless things (like onDamage events on siege, cavalry, and infantry) that will slow the game down and not give any benefits.
What's wrong with function objects? Is there a particular reason why you want your onDamage functions to be methods instead? Also how come are you willing to sacrifice so much performance for syntax candy?
Type casting an arbitary number into a struct is a very bad idea. In order to make it properly work with structs, you'd need to have a linked list system to align structs and unit IDs properly. At this point you've got a lot of complications to make something that is in all likelyhood slower than attatching by GC.
Arbitary isn't the right word here, unless if that's what you would describe structs. The indexes are allocated almost exactly the same as structs with dynamic arrays of size 2. The only difference in use is that the UnitIndexer's indexes don't neccessarely start from 1.
In an earlier post I already showed the struct allocation algorithm and the one of UnitIndexer side by side. Would you care to explain what exactly makes the UnitIndexer's version arbitary?
When I have said that you can typecast the system's indexes to structs I obviously didn't mean any struct, but a struct that you use only for the systems indexes.
I don't understand these three things: why would you want to align structs and unit IDs, why would you use linked lists for doing so and what are these complications your talking about. (examples == thx) (other than using methods as function objects)
It's funny how you estimate that GetUnitUserData + array (lookup or write) is slower than GC.
Quote:
Originally Posted by Captain Griffen
If you really feel the need to use a system like this, you might want to use Handle Array Index Library by Strilanc, which fast, secure and doesn't use up the UnitUserData option.
Seems like you missed something rather obvious. This is essentially just an improved version of PUI. Why didn't you suggest it? Why suggest something slower?
Also UnitIndexer is secure.
EDIT: Ok I removed from the readme the part that said that you can typecast to structs. This is to avoid any more unneccessary hassle about it. You still can typecast the integers if you dont use the structs methods as function objects and if you don't create or destroy that struct type yourself.
Also if no one will even begin to understand how this extremely simple and efficient system works I might bother to make an example of using the system and challenge anyone willing to accomplish the same more efficiently with another system (or no system).
Arbitary isn't the right word here, unless if that's what you would describe structs. The indexes are allocated almost exactly the same as structs with dynamic arrays of size 2. The only difference in use is that the UnitIndexer's indexes don't neccessarely start from 1.
In an earlier post I already showed the struct allocation algorithm and the one of UnitIndexer side by side. Would you care to explain what exactly makes the UnitIndexer's version arbitary?
When I have said that you can typecast the system's indexes to structs I obviously didn't mean any struct, but a struct that you use only for the systems indexes.
They won't necessarily be the same number for the same units. Fact. That makes your numbering system arbitary. Type casting between them is a bad idea.
Quote:
I don't understand these three things: why would you want to align structs and unit IDs, why would you use linked lists for doing so and what are these complications your talking about. (examples == thx) (other than using methods as function objects)
It's funny how you estimate that GetUnitUserData + array (lookup or write) is slower than GC.
What you seem to be suggesting people do would be faster, but completely and utterly unsafe. That's vital. The safe adaptation (ie: linked lists) would then be slower.
Quote:
Seems like you missed something rather obvious. This is essentially just an improved version of PUI. Why didn't you suggest it? Why suggest something slower?
Also UnitIndexer is secure.
I see no real use for this.
Quote:
EDIT: Ok I removed from the readme the part that said that you can typecast to structs. This is to avoid any more unneccessary hassle about it. You still can typecast the integers if you dont use the structs methods as function objects and if you don't create or destroy that struct type yourself.
Also if no one will even begin to understand how this extremely simple and efficient system works I might bother to make an example of using the system and challenge anyone willing to accomplish the same more efficiently with another system (or no system).
If you don't type cast, then you have to use a linked list, which makes it slower. If you do type cast, all hell breaks loose.
__________________
Quote:
[20-00-13] MasterHaosis: Because I am retarded douchebag [20-15-23] MasterHaosis: I am faggot, retard and such.. [20-21-22] MasterHaosis: because I am chat moderator. And you dont have right to speak against me ~Void~: Knock it off, for fuck's sake. Get that bullshit out of your signature and stop being so immature. Let it die out.