- Joined
- Mar 27, 2012
- Messages
- 3,232
(Design stuff. Might not be interesting for most.)
So recently I've been redesigning a lot of my code.
When making my buff system I realized something fairly important - many effects that I wrote for using with buffs would actually make tons of sense as more generic code. For instance, although a buff can put special effects on your unit, so can items, passive abilities and maybe something else.
My code didn't account for that possibility at all, so I continued to think about what it is exactly that I want.
Eventually I realized that what I really want is a way to treat every single thing as an interchangeable object. That is, to use object-oriented programming to a degree that I've never seen done with structs. Heck, it might even be pointlessly complicated when using structs for it.
I created this piece of code to act as the core of everything:
As anyone reading the code might see, this library does very little on its own. All it does is allow creation of generic object types, which can be organized in a tree-like structure. Note that all types are created equal, meaning they share the same arrays and as such, the amount of objects can quickly get out of hand. In my stress tests of spells I have reached around 5k objects by spamming just 1 spell.
It lagged a lot, of course, but I imagine there are ways to reach the limit without killing the game, so although I hope not to use it, I made sure it would be possible to extend the number of indices through changing a single variable.
A considerable pitfall with my design - The system so far does not allow subtypes, because I couldn't figure out a way to do typechecks reasonably in a way that actually makes things easier.
As such, I can easily make the type "buff", but I won't have any explicit way of making specific types of buffs.
It's not a problem though, as I can just add a new type per bufftype, which would also provide me a handy way to create buffs as autocreating structures.
That is, I can make a bufftype (a separate type per bufftype) object add other objects to its parents - namely the effects of the buff.
One might ask why I would make a system for this instead of just linking things in code. The answer is that I can't know every possible interaction. It could very well be that a hero(1.) has an item(2.), which has an ability(3.) that spawns a minion(4.) that has its own passive(5.), which burns units around it by applying a buff on them(6.).
I could want to make it so that this item is some kind of a king-of-the-hill type relic, in which case it would make tons of sense to cancel its effects upon dropping it(through death). However, this means that I would have to hardcode that when this happens, the item checks for its own minions and removes them while paying respect to every single aspect of their existence(the immolate ability(6.)).
If I hardcoded things like this it would certainly be somewhat easier initially, but the code itself would be a mess and I wouldn't be able to maintain it at all. Every little change would have the potential to create tons of problems.
Yes, I was bored. But I also want to know what other people think of this kind of a unified approach. It has simplified my coding quite a lot, while still letting me keep absolute control over everything.
So recently I've been redesigning a lot of my code.
When making my buff system I realized something fairly important - many effects that I wrote for using with buffs would actually make tons of sense as more generic code. For instance, although a buff can put special effects on your unit, so can items, passive abilities and maybe something else.
My code didn't account for that possibility at all, so I continued to think about what it is exactly that I want.
Eventually I realized that what I really want is a way to treat every single thing as an interchangeable object. That is, to use object-oriented programming to a degree that I've never seen done with structs. Heck, it might even be pointlessly complicated when using structs for it.
I created this piece of code to act as the core of everything:
JASS:
library Object requires Utils
globals
public constant integer MaxIndices = 8191//Default is 8191. Any number above it carries a (small) performance penalty and adds some lines of code.
public integer array Type[MaxIndices]
public integer array Parent[MaxIndices]
public integer array FirstChild[MaxIndices]
public integer array ChildCount[MaxIndices]
public integer array Next[MaxIndices]
public integer array Prev[MaxIndices]
public string array TypeName
public trigger array CreateMethod
public trigger array DestroyMethod
public trigger array AddMethod
public trigger array RemoveMethod
public trigger array RefreshMethod
public integer Types = 0
public integer Allocs = 0
public integer array Recycle[MaxIndices]
public integer Recycles = 0
public integer Object
endglobals
public function RegisterType takes string name returns integer
set Types = Types + 1
set TypeName[Types] = name
return Types
endfunction
public function RegisterTypeMethods takes integer otype, code create,code destroy,code add,code remove, code refresh returns nothing
set CreateMethod[otype] = CreateTrigger()
call TriggerAddCondition(CreateMethod[otype],Condition(create))
set DestroyMethod[otype] = CreateTrigger()
call TriggerAddCondition(DestroyMethod[otype],Condition(destroy))
set AddMethod[otype] = CreateTrigger()
call TriggerAddCondition(AddMethod[otype],Condition(add))
set RemoveMethod[otype] = CreateTrigger()
call TriggerAddCondition(RemoveMethod[otype],Condition(remove))
set RefreshMethod[otype] = CreateTrigger()
call TriggerAddCondition(RefreshMethod[otype],Condition(refresh))
endfunction
public function CreateId takes nothing returns integer
local integer id
if Recycles == 0 then
set Allocs = Allocs + 1
set id = Allocs
else
set id = Recycle[Recycles]
set Recycles = Recycles - 1
endif
return id
endfunction
public function Create takes integer otype returns integer
local integer obj = CreateId()
debug if otype > Types or otype < 1 then
debug call Print("CreateObject: Invalid otype"+"("+I2S(otype)+")")
debug endif
set Type[obj] = otype
set Object = obj
call TriggerEvaluate(CreateMethod[otype])
return obj
endfunction
public function Add takes integer obj,integer parent returns nothing
local integer next
local integer prev
if ChildCount[parent] == 0 then
set FirstChild[parent] = obj
set Next[obj] = obj
set Prev[obj] = obj
else
set next = FirstChild[parent]
set prev = Prev[next]
set Next[prev] = obj
set Prev[next] = obj
endif
set ChildCount[parent] = ChildCount[parent] + 1
set Parent[obj] = parent
set Object = obj
call TriggerEvaluate(AddMethod[Type[obj]])
endfunction
public function Remove takes integer obj returns nothing
local integer parent = Parent[obj]
local integer next
local integer prev
set Object = obj
call TriggerEvaluate(RemoveMethod[Type[obj]])
if ChildCount[parent] > 1 then
if obj == FirstChild[parent] then
set FirstChild[parent] = next
endif
set next = Next[obj]
set prev = Prev[obj]
set Next[prev] = next
set Prev[next] = prev
set ChildCount[parent] = ChildCount[parent] - 1
else
set FirstChild[parent] = 0
set ChildCount[parent] = 0
endif
set Next[obj] = 0
set Prev[obj] = 0
endfunction
public function Destroy takes integer obj returns nothing
if Parent[obj] != 0 then
call Remove(obj)
endif
loop
exitwhen FirstChild[obj] == 0
call Destroy(FirstChild[obj])
endloop
set Object = obj
call TriggerEvaluate(DestroyMethod[Type[obj]])
set Type[obj] = 0
set Recycles = Recycles + 1
set Recycle[Recycles] = obj
endfunction
endlibrary
It lagged a lot, of course, but I imagine there are ways to reach the limit without killing the game, so although I hope not to use it, I made sure it would be possible to extend the number of indices through changing a single variable.
A considerable pitfall with my design - The system so far does not allow subtypes, because I couldn't figure out a way to do typechecks reasonably in a way that actually makes things easier.
As such, I can easily make the type "buff", but I won't have any explicit way of making specific types of buffs.
It's not a problem though, as I can just add a new type per bufftype, which would also provide me a handy way to create buffs as autocreating structures.
That is, I can make a bufftype (a separate type per bufftype) object add other objects to its parents - namely the effects of the buff.
One might ask why I would make a system for this instead of just linking things in code. The answer is that I can't know every possible interaction. It could very well be that a hero(1.) has an item(2.), which has an ability(3.) that spawns a minion(4.) that has its own passive(5.), which burns units around it by applying a buff on them(6.).
I could want to make it so that this item is some kind of a king-of-the-hill type relic, in which case it would make tons of sense to cancel its effects upon dropping it(through death). However, this means that I would have to hardcode that when this happens, the item checks for its own minions and removes them while paying respect to every single aspect of their existence(the immolate ability(6.)).
If I hardcoded things like this it would certainly be somewhat easier initially, but the code itself would be a mess and I wouldn't be able to maintain it at all. Every little change would have the potential to create tons of problems.
Yes, I was bored. But I also want to know what other people think of this kind of a unified approach. It has simplified my coding quite a lot, while still letting me keep absolute control over everything.