Heres a new, in my opinion better version of the observer. notice that there are couple of big changes in comparison to previous version. In previous version I had put it as observers responsibility to activate the next function in queue, now observer has only one job to do, it is waiting for certain message to appear, and then starts one function. In this version, observer cant have several functions in queue, but only one. Hence it is now the card1 functions responsibility to handle the queue itself.
Also, Observer doesnt take care of removing functions from its table anymore, but once again, functions themselves have to take care of it themself, this gives possibilties like having a counter like the one in example code.
Good thing however is, that observer is now both much easier to read and understand as well as more flexible.
Code: Select all
observer = { table = {} }
Function observer:add(name, message, func, ...) /* ... part are possible arguments */
self.table[name] = { message=message, func=func, arg=arg }
EndFunction
Function observer:remove(name)
self.table[name] = Nil
EndFunction
Function observer:execute(name)
subtable = self.table[name]
arg = subtable.arg
subtable.func(Unpack(arg))
EndFunction
Function observer:inform(message)
For name, itemtable In Pairs(self.table)
If itemtable.message = message Then observer:execute(name)
Next
EndFunction
Function card1(n)
Local temptable = {
[1] = { funcinadvance = init_up, name="card1", message="up is done", func=card1, arg=2, },
[2] = { funcinadvance = Init_down, name="card1", message="down is done", func=card1, arg=3, },
[3] = { funcinadvance = init_up, name="card1", message="up is done", func=card1, arg=4, },
[4] = { funcinadvance = Init_down, name="card1", message="down is done", func=card1, arg=5, },
[5] = { funcinadvance = init_up, name="card1", message="up is done", func=card1, arg=6, },
[6] = { funcinadvance = Init_down, name="card1", message="down is done", func=card1, arg=7, },
[7] = { funcinadvance = init_up, name="card1", message="up is done", func=card1, arg=8, },
[8] = { funcinadvance = Init_down, name="card1", message="down is done", func=card1, arg=9, },
[9] = { funcinadvance = init_up, name="card1", message="up is done", func=card1, arg=10, }
}
If n = 0 then n=1
observer:remove("card1")
If n < 10
temptable[n].funcinadvance()
name="card1"
message = temptable[n].message
func = temptable[n].func
arg = temptable[n].arg
observer:add(name, message, func, arg)
EndIf
EndFunction
Function init_upcounter()
var_upcounter = 0
TextOut(100, 300, "upcounter: "..var_upcounter, {name="upcounterlayer"} )
observer:add("upcounter", "up is done", upcounter)
EndFunction
Function upcounter()
var_upcounter=var_upcounter + 1
SetLayerStyle("upcounterlayer", {text="upcounter: "..var_upcounter} )
If var_upcounter = 3
observer:remove("upcounter")
EndIf
EndFunction
Function init_up()
TextOut(300, 300, "press up", {name="up"})
SetInterval(2, up, 15)
EndFunction
Function up()
Local pressed = False
If IsKeyDown("UP") = True Then pressed = True
If pressed = True
RemoveLayer("up")
ClearInterval(2)
Observer:inform("up is done")
EndIf
EndFunction
Function init_down()
TextOut(300, 400, "press down", {name="down"})
SetInterval(3, down, 15)
EndFunction
Function Down()
Local pressed = False
If IsKeyDown("DOWN") = True Then pressed = True
If pressed = True
RemoveLayer("down")
ClearInterval(3)
Observer:inform("down is done")
EndIf
EndFunction
Function updategraphics()
If dir = 0 Then x=x-3
If dir = 1 Then x=x+3
If x <100 Then dir = 1
If x >500 Then dir = 0
SetLayerStyle("text1", {x=x})
EndFunction
EnableLayers
TextOut(100, 100, "moving text", {name="text1"})
SetInterval(1, UpdateGraphics, 15)
card1()
init_upcounter()
Repeat
WaitEvent()
Forever
I am explaining things now.
First the Observer itself.
Observer has 4 commands:
Observer:Add(name, message, func, arg) - Args are optional.
Observer:Remove(name)
Observer:Execute(name)
Observer:Inform(message)
edit: I forgot the Observer:Inform from original message. Observer:Inform is the command that functions use to tell that something have happened, like "Observer:Inform("player have died")" And it is also the part that checks through the list of observers if any of the observers are waiting for that message. If one or more of the observers are waiting for that message, then it executes the function that was put with Observer:Add to be executed if that message is received.
- end edit -
Remove and Execute are simple to use. Simply give the name and Observer handles the rest, like adding the possible arguments to the execution.
Observer has a table inside it (observer.table) which contains each observer. Each observer has a unique name that you pick at point you use Observer:Add. This is so that functions outside of Observer would be able to Remove functions from observer list.
message is the message that the observer will be waiting for. When it receives that message, then function in func is being executed using observer:execute(name)
in observer:execute(name) it is simply starting that function with the args: "observer[name].func(arg)" There dont have to be any args, it is simply optional. For example the example "upcounter" function doesnt use any args, while "card1" function specifically uses one arg to make it possible to execute each function in a row (that card1 takes care of, observer is only handling one of them at a time).
the example functions. Starting from easier one, the upcounter function.
I start it from init_upcounter, where it sets "var_upcounter" as 0 and sets a text layer called "upcounterlayer".
After this it uses Observer:Add to add the upcounter() function to the observer list. Now each time a message "up is done" is received by the observer, "upcounter()" function is being executed. Notice that this "upcounter()" function is not being removed from observer but stays there, so that even "up is down" message is received several times, it is still being executed.
"upcounter" functions task is simple. "var_upcounter = var_upcounter + 1" and then it changes the text to show in practice how many times "up" key have been pressed (or "up is done" have happened).
However, after var_upcounter becomes 3, it will remove "upcounter" function from the observers list by using " observer:Remove("upcounter") ". After this, even if "up is done" happens, it doesnt execute "upcounter" anymore, as it is not listening to messages anymore in observer list, as it have been removed from it.
The harder function is the "card1" function.
In this function the idea is that there are several functions i want to be executed in a row which all require some user interaction (in this case either to press "up" arrow or "down" arrow)
At beginning of that function I am making local temptable. You could basically create a permanent table, but i came to conclusion that it is best to create local table each time, since you need that table only on that function and after that it becomes useless, until you return to that function. By using it as local, it doesnt stick to computer memory but gets cleaned out each time until being created again when "card1" function is being executed.
In "card1" function I am using optional arg called "n", this is to help it execute orderd (functions) in queue.
So hence when I am sending "observer:Add" message, I am also sending Arg which will be telling the number of the next function, so that next time it comes back to "card1" it will be executing that numbered function instead of the same one.
While that temptqable is otherwise having the usual "Observer:Add" stuff, it also includes this "funcinadvance".
Idea is that I will first execute inside "card1" function that funcinadvance function, which in this case is always either "init_down" or "init_up". After this I am telling it to "Observer:add" the stuff so it knows that when that "up" or "down" functions send their message of "up is done" or "down is done" it knows to start the "card1" function again with the arg number to tell which numbered function to start.
Notice that each time "card1" function is being executed, also the Observer:Remove("card1") is being executed. This means also the first time when there is no such observer on the list. However, this is not a problem as Observer:Remove is simply making some certain place "Nil", hence this can be done this way, although it is unnecessary the first time round.
Notice also that I am using "if n=0 then n=1"
Basically this line could be removed and at execution simply use "card1(1)" instead, but by using this "if n=0 then n=1" it is easier for the programmer to use this, as he dont need to know the innerworks of "card1", all he needs to do is to use function "card1()" as it is, instead of having to know that it needs to be used as "card(1)" for example.
I believe this observer is pretty good and flexible now as can be seen from the example. Both upcounter and card1() are waiting for same interaction at some points "up is done"-message. And it works very nicely. Also, if i wanted to add new observer, like to calculate down presses, all i would need to do is to make another function and add it to observer list to wait for "down is done" message, without any need to touch the actual "down" function.
When compared to using gamestates, in case like this i would also need to change the "down" function to either have some state to let the counter know it is supposed to do something, or (simpler) to add one more line of code to "down" function telling to execute "downcounter()".
And then if i decided that i dont need that downcounter anymore, in addition to removing the function, i would also need to remember to remove it from "downcounter", while now i only need to simply remove it from ever entering to observer.
This is even more true in case i would be having a function that would be reacting in several places, then i would either add this same function of "counter()" to each place and try to remember to remove it from all 10 places, versus to simply removing the function from observer list.
I believe this is somewhat good solution to most cases where code waits for some interaction from user while it cant stop the whole program to wait for the interaction to happen.