Someone recently posted a question about defining UDT arrays in a function. Well, here is something along those lines, yet way more useful and good for object orientation.
Before long the procedural nature of the DarkBASIC professional array system brings you to the realization that the DBP command-set could really do with storing arrays in arrays, and functions in arrays. Afterall, in practical organization, lists often contain lists.
One work around is to use memblocks or
Matrix1 banks to store arrays into other arrays. You can even store function addresses into arrays using the Matrix1 pointer system.
Another way is to store a long list of items which store links between themselves forming a series of items sharing common paths.
But how about a method which retains functions and arrays as they are without much conversion; do you always have to convert resources everytime you want to take advantage of object orientation, and the array system? Do you always need to use indexing to pair child items with parents and then iterate these lists when something needs to be removed?
No.. You do not have to always work with memblocks and you do not have to use link lists. Although there is nothing wrong with the combination.
However, one of the most overlooked features of Matrix1 utilities is the coroutine feature. Coroutines are functions which can process their actions and yield the program flow to other functions and the main loop without loss of information. These sub-programs are often used to operate primarily with local variables. They tend to be used for processing, but like I always say regarding tools; it's not what it is called, it's what it does that counts.
Coroutines can also be used as class instances that contain static storage in the form of local arrays and variables, which inturn can produce inner coroutines, thus members of their own. Much like memblocks being able to store arrays; it is possible to store arrays in these functions, and more interestingly treat functions as if they where array elements; since coroutines are numbered resources.
In the following example, two classes of coroutines store and update instances of a type called character. One of the classes has the ability to save, the other class only updates. The main program communicates with globals, and can also communicate with resources such as memblocks and images.
// Chris Tate - www.binarymodular.com
// Class example - 19/04/13
Type CharacterType
Name$
Health#
Coroutine ` Unused, but could be useful for some situations
endtype
Global Load
Global Save
Global Dim NewCharacters() as CharacterType
NewCharacter("Tom", 100)
NewCharacter("Dick", 100)
NewCharacter("Harry", 100)
SpawnCharacters(0,"Default") ` Spawn in default class (coroutine function prefix) with generated id
NewCharacter("Patrick", 50)
NewCharacter("Chris Tate", 330)
SpawnCharacters(3, "Enemy") ` This time specify a coroutine ID which will be created if non-existant.
` We will create a character derived class called enemy (function prefix) making sure it is the correct name of the function minus the 'CharacterClass' suffix
Do
` Update all coroutines (unless one of them overrides the process)
Next Coroutine
Print "Press space to add an enemy. Press up to save. Press down to load."
i = 0
repeat
nice wait 100
inc i
until scancode() or i => 49
If Spacekey()
NewCharacter("New guy", Rnd(300)+100)
SpawnCharacters(3, "Enemy")
endif
If Upkey()
Save = 1
endif
If Downkey()
Load = 1
endif
loop
End
Function SpawnCharacters(iDefaultCoroutine, sClass$)
If iDefaultCoroutine = 0
iDefaultCoroutine = Reserve Free Coroutine()
Create Coroutine iDefaultCoroutine, sClass$ + "CharacterClass"
Switch To Coroutine iDefaultCoroutine ` Enter into UpdateCharacter function now!
Else
If Coroutine Exist(iDefaultCoroutine)
` Go to the character class (at least it should be the same class)
` and add the new characters
Switch To Coroutine iDefaultCoroutine
else
` Create it since it is missing
Create Coroutine iDefaultCoroutine, sClass$ + "CharacterClass"
Switch To Coroutine iDefaultCoroutine ` Enter into UpdateCharacter function now!
endif
endif
` Return the ID
endfunction iDefaultCoroutine
Function NewCharacter(name$, health#)
Array Insert At Bottom NewCharacters()
NewCharacters().Name$ = Name$
NewCharacters().Health# = health#
EndFunction
Function DefaultCharacterClass()
` This is class, in the form of a function; there can be other ways to update characters
Local Dim Characters() as CharacterType ` These characters only exist in this function stack
Do
` Add characters
If Array Count(NewCharacters()) => 0
For i = 0 to Array Count(NewCharacters())
Array Insert At Bottom Characters()
Characters() = NewCharacters(i)
next i
Empty array NewCharacters()
Switch To Coroutine 0 ` Go back to the main program for now
EndIf
` Update my character instances
n = Array Count(Characters())
For i = 0 to n
Print "Updated: "; Characters(i).Name$ ; " of default class Health: "; Characters(i).Health#
Next i
` Save
If Save = 1
Make Memblock From Array 1, Characters()
If File Exist( "DefaultCharacters.dat" ) Then Delete File "DefaultCharacters.dat"
Open To Write 1, "DefaultCharacters.dat"
Write File 1, Array Count( Characters() )
Write Memblock 1, 1
Delete Memblock 1
Close File 1
print "-------------------------"
Print "Saved default characters"
Print
Save = 0
endif
If Load = 1
If File Exist( "DefaultCharacters.dat" )
Open To Read 1 , "DefaultCharacters.dat"
Read File 1, n
Read Memblock 1, 1
Local Dim lc(n) as CharacterType
Make Array From Memblock lc(), 1
Delete Memblock 1
print "-------------------------"
Print "Loading "; n + 1 ; " characters from disk"
For i = 0 to n
Array Insert At Bottom Characters()
Print "Loading "; lc(i).Name$
` Note that sometimes there is a discreprency with array strings
` It may be necessary to convert UDT arrays with strings
` using other methods or analyse and clean up the memblock itself
Characters() = lc(i)
next i
Print "Done"
Print
Empty Array lc()
Load = 0
endif
endif
` Done updating, update next character stack or proceed with main program
Next Coroutine
loop
endfunction
Function EnemyCharacterClass()
` This is class, in the form of a function; there can be other ways to update characters
Local Dim Characters() as CharacterType
` These characters only exist in this function stack
Do
` Add characters
If Array Count(NewCharacters()) => 0
For i = 0 to Array Count(NewCharacters())
Array Insert At Bottom Characters()
Characters() = NewCharacters(i)
next i
Empty array NewCharacters()
Switch To Coroutine 0 ` Go back to the main program for now
EndIf
` Update my character instances
n = Array Count(Characters())
For i = 0 to n
Print "Updated: "; Characters(i).Name$ ; " of enemy class. Health: "; Characters(i).Health#
Next i
` Done updating, update next character stack or proceed with main program
Next Coroutine
loop
endfunction
Now you are limited to 4096 coroutines and 2GB of RAM for 32bit apps; so this requires smart thinking and economy. Now 2GB ram for a video game is fine, but 4096 instances of a class would be too small to contain a medium to large game; better to treat these routines as classes themselves which store all their instances; for example if it is possible to have more than 4096 different entities in the application at once, make entities more comprehensive, each class function can handle various kinds of entities with similar properties.
Compared to types, you have the increased freedom of being able to do pretty much anything you can do with DBP inside of a class function; this means such classes can be composed of variable parts, yet share functionality with composite functions and use property functions to enumerate or transform resources all inside of a class function.
So coroutines are great for multi-processing; but for DBP they're also a solution for storing and dealing with complex information using similar types and parameters.
Uses for class functions include
* Game levels that can switch without being unloaded, and be unloaded when complete.
* Styles used to decorate how information is drawn
* Entities with customizable behaviour
* Multi-level hierarchies
* Speedy data access using locals
Remember to consider using the stack-size parameter of the coroutine constructer to allocate enough memory for your arrays and datatypes.
See the Matrix1 documentation.
You can check the memory size of arrays by converting them to memblocks. You can allow other functions to communicate with an array instance in the coroutine stack using array pointers and array linking.