- Joined
- May 26, 2009
- Messages
- 1,829
Introduction
Implementing the method
Creating a spell core
Fetching data from the Spell Core
Wrap Up
When working with large spells with many components data management poses a large challenge - while it's simple to pass data through each part as needed - it leads to lots of data duplication, normally to avoid recalculating data. This tutorial explains how to use Spell Cores in order to reduce data duplication while maintaining efficiency with minimal calculations, as it is dealing with large works this tutorial assumes you know how to make an approvable spell.
Notes:
- This method be used with linked lists of hash tables - complications ensue when using dynamic indexing.
- It's recommended you read Building large spells: StageIDs before continuing with this tutorial
The ConceptNotes:
- This method be used with linked lists of hash tables - complications ensue when using dynamic indexing.
- It's recommended you read Building large spells: StageIDs before continuing with this tutorial
What is a spell core? - A Spell Core is generally the first component of any spell. But it has these key defining features:
The method is an implementation of Pointers in conjunction with Dynamic indexing/Linked Lists/Hash tables
- It is used to store all non-unique information needed for the spell along with its own information.
- It persists until the end of the spell (or until the end of when non-unique information is needed)
- It does not need to refer to anything physically (but it can and in most cases should)
How is it useful?- It persists until the end of the spell (or until the end of when non-unique information is needed)
- It does not need to refer to anything physically (but it can and in most cases should)
- The spell core has one key use: Whenever referring to non-unique information in a spell the information is fetched from the spell core, removing the need to duplicate information or transfer it to each part of the spell as need be, this results in one-time calculations and minimal data duplication at the same time.
- However it can also be used to tie components of spells to other parts as information such as location can be taken from the spell core
- By utilising multiple Spell Cores in a single spell it's possible to create a "Core Tree" where any branch or leaf on the tree can easily access data from the Root of the tree but also other information like location from each branch that precedes it
- If the two uses above are the only thing useful to you (as your existing method already minimises data duplication and calculations) these things will be explained in the "Advanced Spell Cores" tutorial (TBA)
Technically speaking a spell core can be nothing more than an extra instance in your spell and be purely data but it's less efficient than using something actually useful in your spell- However it can also be used to tie components of spells to other parts as information such as location can be taken from the spell core
- By utilising multiple Spell Cores in a single spell it's possible to create a "Core Tree" where any branch or leaf on the tree can easily access data from the Root of the tree but also other information like location from each branch that precedes it
- If the two uses above are the only thing useful to you (as your existing method already minimises data duplication and calculations) these things will be explained in the "Advanced Spell Cores" tutorial (TBA)
The method is an implementation of Pointers in conjunction with Dynamic indexing/Linked Lists/Hash tables
Implementing the method
For the sake of simplicity we'll be assuming the indexing method used for this spell is Dynamic indexing as indexing is simpler - However this method is significantly improved by using linked lists or hash tables
Creating a spell core
The key to this method is using a SpellCore variable - you can call it whatever you like and it'll almost always be an integer this is because the value you're storing is the index of the Spell Core or the HandleID of a unit used as one (when using hash tables the SpellCore can be a unit variable) making one is fairly simple:
But what is "All non-unique data"? Well taking the example from Building large spells: StageIDs our turret fires projectiles, all those projectiles deal the same amount of damage, have the same size, etc. and all our slowed units are slowed by the same amount, for the same duration, etc. all this data must be worked out in advance and there's two ways to do that:
If you are using the first listed method only then the uses of this methodology are reserved to the "Advanced Spell Cores" tutorial (TBA)
1) Do what you normally do for your chosen indexing method
2) Is this the Spell Core for this spell?
- If Yes - Set the Spell Core for that index to its own index
3) Done
2) Is this the Spell Core for this spell?
- If Yes - Set the Spell Core for that index to its own index
- Apply all non-unique data to this index
- If No - Set the Spell Core for that index to match the relevant Spell Core for that instance3) Done
Is Spell Core
Isn't Spell Core
GUI
JASS
-
Set MaxIndex = (MaxIndex + 1)
-
Set SpellCore[MaxIndex] = MaxIndex
-
-------- Apply all non-unique data --------
-
-------- Set up everything else --------
JASS:
set MaxIndex = MaxIndex + 1
set SpellCore[MaxIndex] = MaxIndex
//Apply all non-unique data
//Set up everything else
GUI
JASS
-
Set MaxIndex = (MaxIndex + 1)
-
Set SpellCore[MaxIndex] = SpellCore[Index]
-
-------- Set up everything else --------
JASS:
set MaxIndex = MaxIndex + 1
set SpellCore[MaxIndex] = Index
//Set up everything else
But what is "All non-unique data"? Well taking the example from Building large spells: StageIDs our turret fires projectiles, all those projectiles deal the same amount of damage, have the same size, etc. and all our slowed units are slowed by the same amount, for the same duration, etc. all this data must be worked out in advance and there's two ways to do that:
- All data is worked out in a configuration specifically stating the value for every level (Passes level to each stage, does not use a spell core)
- Data is worked out at the start of an instance using "Base + (PerLevel * level)" values where Base and PerLevel for each value is stated specifically in the configuration (uses a Spell Core)
- Data is undetermined entirely by configuration but will be determined by another factor (e.g. Damage = Targets in AOE * DamagePerTarget or Damage = TimeChannelledFor * DamagePerSecondChannelled)
- Data is worked out at the start of an instance using "Base + (PerLevel * level)" values where Base and PerLevel for each value is stated specifically in the configuration (uses a Spell Core)
- Data is undetermined entirely by configuration but will be determined by another factor (e.g. Damage = Targets in AOE * DamagePerTarget or Damage = TimeChannelledFor * DamagePerSecondChannelled)
If you are using the first listed method only then the uses of this methodology are reserved to the "Advanced Spell Cores" tutorial (TBA)
Fetching data from the Spell Core
Whenever you need to fetch information stored in the spell core you nest indices within each other in order to get back to the Spell Core's index in the following format: Data[SpellCore[Index]]
So say we wanted to damage a unit because our projectile from our turret has hit a unit
Practical ApplicationSo say we wanted to damage a unit because our projectile from our turret has hit a unit
GUI
JASS
-
Unit - Cause (Turret[SpellCore[Index]]) to damage (Target) dealing (ProjDamage[SpellCore[Index]]) damage of attack type TurretAttackType and damage type TurretDamageType
JASS:
call UnitDamageTarget(Turret[SpellCore[Index]], Target, ProjDamage[SpellCore[Index]], true, false, TurretAttackType, TurretDamageType)
So let's take our example again and see how using a SpellCore with it would look like as opposed to not using one, we'll assume the data has already been applied to the Spell Core correctly and that the Turret is the Spell Core. We'll also only do it for the projectile damage to showcase how much is saved for three values (Turret, health, and mana damage) by using a Spell Core over not using one
As can be seen, while if we were transferring one piece of information the code lines and efficiency remain the same - but for every additional piece of information needed a line is saved, so for in your large spells (in which large quantities of data will be needed at every stage) the amount of data duplication saved by using this method becomes phenomenal and cuts down the code lines you're using
RecyclingUsing Spell Core
Not using Spell Core
GUI
JASS
-
ST Slowing Turret Loop
-
Events
-
Time - Every 0.03 seconds of game time
-
-
Conditions
-
Actions
-
For each (Integer Index) from 1 to MaxIndex, do (Actions)
-
Loop - Actions
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
StageID[Index] Equal to 1
-
-
Then - Actions
-
-------- Turret Actions --------
-
-------- (...) --------
-
-------- Turret fires a projectile! --------
-
Set MaxIndex = (MaxIndex + 1)
-
Set StageID[MaxIndex] = 2
-
Set SpellCore[MaxIndex] = Index
-
-------- Set up projectile data --------
-
-
Else - Actions
-
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
StageID[Index] Equal to 2
-
-
Then - Actions
-
-------- Projectile Actions --------
-
-------- (...) --------
-
-------- Projectile Hits a unit! --------
-
Unit - Cause (Turret[SpellCore[Index]]) to damage (Target) dealing (ProjHealthDamage[SpellCore[Index]]) damage of attack type TurretAttackType and damage type TurretDamageType
-
Unit - Set mana of (Target) to (Mana of (Target) - ProjManaDamage[SpellCore[Index]])
-
-------- Index unit --------
-
Set MaxIndex = (MaxIndex + 1)
-
Set StageID[MaxIndex] = 3
-
-------- Set up unit data --------
-
-
Else - Actions
-
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
StageID[Index] Equal to 3
-
-
Then - Actions
-
-------- Slowed Unit Actions --------
-
-
Else - Actions
-
-
-
-
-
JASS:
function ST_Slowing_Turret_Loop takes nothing returns nothing
local integer i = 0
loop
exitwhen i > MaxIndex
if StageID[i] == ST_TurretStageID() then
//Turret Actions
// (...)
//Turret fires a projectile!
set MaxIndex = MaxIndex + 1
set StageID[MaxIndex] = ST_ProjectileStageID()
set SpellCore[MaxIndex] = i
//Set up projectile data
elseif StageID[i] == ST_ProjectileStageID() then
//Projectile Actions
// (...)
//Projectile hits a unit!
call UnitDamageTarget(Turret[SpellCore[i]], Target, ProjHealthDamage[SpellCore[i]], true, false, TurretAttackType, TurretDamageType)
call SetUnitState(Target, UNIT_STATE_MANA, GetUnitState(Target, UNIT_STATE_MANA) - ProjManaDamage[SpellCore[i]])
//Index unit
set MaxIndex = MaxIndex + 1
set StageID[MaxIndex] = ST_UnitStageID()
//Set up slowed unit Data
else
//Slowed unit Actions
endif
i = i + 1
endloop
endfunction
GUI
JASS
-
ST Slowing Turret Loop
-
Events
-
Time - Every 0.03 seconds of game time
-
-
Conditions
-
Actions
-
For each (Integer Index) from 1 to MaxIndex, do (Actions)
-
Loop - Actions
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
StageID[Index] Equal to 1
-
-
Then - Actions
-
-------- Turret Actions --------
-
-------- (...) --------
-
-------- Turret fires a projectile! --------
-
Set MaxIndex = (MaxIndex + 1)
-
Set StageID[MaxIndex] = 2
-
-------- Set up projectile data --------
-
Set Turret[MaxIndex] = Turret[Index]
-
Set ProjHealthDamage[MaxIndex] = ProjHealthDamage[Index]
-
Set ProjManaDamage[MaxIndex] = ProjManaDamage[Index]
-
-
Else - Actions
-
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
StageID[Index] Equal to 2
-
-
Then - Actions
-
-------- Projectile Actions --------
-
-------- (...) --------
-
-------- Projectile Hits a unit! --------
-
Unit - Cause (Turret[Index]) to damage (Target) dealing (ProjHealthDamage[Index]) damage of attack type TurretAttackType and damage type TurretDamageType
-
Unit - Set mana of (Target) to (Mana of (Target) - ProjManaDamage[Index])
-
-------- Index unit --------
-
Set MaxIndex = (MaxIndex + 1)
-
Set StageID[MaxIndex] = 3
-
-------- Set up unit data --------
-
-
Else - Actions
-
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
StageID[Index] Equal to 3
-
-
Then - Actions
-
-------- Slowed Unit Actions --------
-
-
Else - Actions
-
-
-
-
-
JASS:
function ST_Slowing_Turret_Loop takes nothing returns nothing
local integer i = 0
loop
exitwhen i > MaxIndex
if StageID[i] == ST_TurretStageID() then
//Turret Actions
// (...)
//Turret fires a projectile!
set MaxIndex = MaxIndex + 1
set StageID[MaxIndex] = ST_ProjectileStageID()
//Set up projectile data
set Turret[MaxIndex] = Turret[Index]
set ProjHealthDamage[MaxIndex] = ProjHealthDamage[i]
set ProjManaDamage[MaxIndex] = ProjManaDamage[i]
elseif StageID[i] == ST_ProjectileStageID() then
//Projectile Actions
// (...)
//Projectile hits a unit!
call UnitDamageTarget(Turret[i], Target, ProjHealthDamage[i], true, false, TurretAttackType, TurretDamageType)
call SetUnitState(Target, UNIT_STATE_MANA, GetUnitState(Target, UNIT_STATE_MANA) - ProjManaDamage[i])
//Index unit
set MaxIndex = MaxIndex + 1
set StageID[MaxIndex] = ST_UnitStageID()
//Set up slowed unit Data
else
//Slowed unit Actions
endif
i = i + 1
endloop
endfunction
As can be seen, while if we were transferring one piece of information the code lines and efficiency remain the same - but for every additional piece of information needed a line is saved, so for in your large spells (in which large quantities of data will be needed at every stage) the amount of data duplication saved by using this method becomes phenomenal and cuts down the code lines you're using
There's a number of things to be aware of when you recycle spells which make use of a spell core structure:
There's a good reason why this method is generally not to be used with dynamic indexing - whenever you recycling indexes move (unlike hash tables and linked lists) meaning that whenever you recycle you must first locate every index which uses the index you're recycling as a spell core (only do this when what you're recycling is a spell core) and update their SpellCore
- When the spell core is recycled the data stored on it should not be accessed by any other components of the spell as the data will be overwritten by another index thus making it inaccurate and causing bugs
- You can use a secondary loop checking for "SpellCore[TempNode] == Node" to locate all indexes which use the current node as a SpellCore, be aware that the Spell Core will often reference itself and thus will find itself when searching this way. You can use this to destroy all components when the Spell Core expires or mark them to change behaviour/recycle/whatever else
- If you are using linked lists you can however use this data for one iteration, the same iteration that the spell core was recycled on (assuming you do not null the data when you recycle it)
- As a consequence generally speaking the Spell Core should outlast all components that reference it, unless they no longer need the data
- If you don't want to keep the physical presence of a Spell Core around longer than the components that use it (for example the turret dies before all bullets do) you can destroy the physical effect of it but keep the data until you know all the other components are dead- As a consequence generally speaking the Spell Core should outlast all components that reference it, unless they no longer need the data
- You can use a secondary loop checking for "SpellCore[TempNode] == Node" to locate all indexes which use the current node as a SpellCore, be aware that the Spell Core will often reference itself and thus will find itself when searching this way. You can use this to destroy all components when the Spell Core expires or mark them to change behaviour/recycle/whatever else
- Linked lists can use TempNode = NextNode[Node] as a starting point as all things referencing the Spell Core will be indexed after it, thus avoiding this collision
There's a good reason why this method is generally not to be used with dynamic indexing - whenever you recycling indexes move (unlike hash tables and linked lists) meaning that whenever you recycle you must first locate every index which uses the index you're recycling as a spell core (only do this when what you're recycling is a spell core) and update their SpellCore
Wrap Up
If you still find it hard to understand, feel free to PM me any time.
Thank you for reading.
~Tank-Commander
Thank you for reading.
~Tank-Commander
Last edited: