DaMOB
Dec 15 2003, 10:22 PM
No particular language but I am having a bit of a time grasping some of it (I am too used to loops).
Take for instance
Game event happens
(now for something I can talk in and thats actool)
If game event is what I am after
Call startof
End
IF donext = 1
call next
End
IF donext = 2
call next2
End
Stop
procedure startof
setconst donext = 0 //so we don't recurse
open a file
setconst filehandle = blah
setconst donext = 1
end
procedure next
setconst donext = 0 //so we don't recurse
read the file, filehandle
close filehandle
setconst donext = 2
end
procedure next2
setconst donext = 0 //so we don't recurse
//something
setconst donext = 0 //all of the routines are done
end
Now granted that is poorly written but I hope it gets across how I think fsm coding works. In Decal when I am in my waiting loop for events is that where I would throw in the loop checking donext as well?
kgober
Dec 16 2003, 07:09 PM
your idea is basically right, though the syntax is kind of clunky.
it might help to think about state machines this way (technically, this whole explanation is bogus, but the 'mathematical' definition of a state machine and the way they're often used in real life are different enough that this explanation should be workable):
an ordinary program uses lots of memory. there's memory for the program code itself, plus memory for all your variables. on top of that, there's a small amount of memory that's used to store 'context'. your 'context' is all the information you need in order to pause and restart the program (which constantly happens on a multitasking operating system).
when the operating system decides it's time for some other program to run, it pauses your program, and saves its context. this includes the contents of all the cpu registers, etc. when it's time for your program to run again, the OS restores all your context and your program resumes exactly where it left off.
now, imagine that every time the OS resumes your program, it doesn't restore your context and *resume* where you left off, instead it restarts your program from the beginning. now, you still have all your variables in memory (they don't get cleared), but many of them are meaningless because they only made sense in the context of what you were doing at the time. suppose you have a variable named 'DelayAmount', that tells you how long to wait for something. knowing after a restart that 'DelayAmount' is '3' doesn't help you if you don't know what it is you were waiting for when your program was interrupted. were you waiting for an item to appear in inventory after a purchase? were you waiting to go from peace mode to combat mode? how long ago did '3' start counting? has it already elapsed?
a state machine is similar to this situation, only worse. because instead of your program always restarting from the same place (the beginning), it might restart from one of many places (each of your 'event handlers' is a potential place for your program to resume running).
the basic idea of a state machine is this: because you don't know what it was that you were doing when you were interrupted, you store a value in a special variable to remember your 'state'. the 'state' encodes both 'what were you doing when you were interrupted' and 'what did you plan to do next' into a single value. every time your program restarts (due to an event handler kicking off), you can look at the state variable to recognize what the event means in the grand scheme of what you were doing, and what you were planning to do next. the event might be a signal to continue with the plan, or it might be a signal to abort the plan and start a new plan (e.g. never mind looting, a monster just spawned next to me). you communicate these things by changing the value of the state variable, for the benefit of the *next* part of your program that runs.
now, the big question. if Windows is able to save and restore your context because it's a multitasking operating system, allowing your programs to resume exactly where they left off, why do you need to bother with a state machine?
two reasons. one of them is event handlers. event handlers start executing unpredictably (again not technically true, but valid for the purpose of this explanation), and you need a way to communicate to them what they should do when a given event occurs. the state machine model can fulfill this requirement.
the second reason has to do with Decal. if you write a native Decal plugin, you can never have a waiting loop (unless you use threads, which don't really solve the problem anyway, so it's best to just assume that you can't). in Decal, a waiting loop will never terminate, because the event you're waiting for will never happen (if it's based on a game event, that is. waiting for a timer to expire is an exception). the reason it won't happen? neither the AC client nor Decal will ever recognize the event, because they won't ever get a chance to receive and process it -- your waiting loop is monopolizing the CPU.
so, in a native Decal plugin, if you can't use waiting loops, what can you do? you can return control to the game, and rely on a future event to start your plugin running again via an event handler. again, because you never know *which* event handler might be the one that's called, you need a way to tell all of them what it was you were doing, and what it is you plan to do next. again, the state machine model can fulfill this requirement.
this explanation is really more than you need I think -- I get the impression that you understand the basics well enough. but perhaps someone else might get some use from it.
-ken
DaMOB
Dec 16 2003, 07:48 PM
Well, what loses me is the lack of looping. Its a hard concept to grasp and even harder to implement when you have been programming with loop logic for 20+ years.
I think I am understanding but as you said its clunky.
Lone
Dec 17 2003, 01:06 AM
I believe there is a small exception to the statement you can't do looping. It is my understanding that you can not do a:
| CODE |
while (wait for something to happen) { } |
but you can do:
| CODE |
for ( x = 0; x < 10; x++) { } |
provided that what you are doing in that loop does not take too much time or delay the client.
In decal you do not code the poll loop checking for events. You tell decal what events you want and write the event handlers. At least that is my limited understanding of it.
DaMOB
Dec 17 2003, 09:46 AM
but after MY EVENT has fired and my routine is hella long so I break it into smaller chunks how do I fire the remain routines one by one? Thats the part I am lost in not the part about checking and acting on an event.
Example:
Cast spell
if fizzle cast again
if need mana go to fill up
that simple example is at least 5 things you have to do and all can take enormous time plus you can't stay in a loop waiting for the spell to fire you must come back to it. Thats what I am not comprehending how to implement.
kgober
Dec 17 2003, 03:38 PM
I tried to avoid saying that you couldn't loop in a plugin, instead saying that 'waiting loops' couldn't be used. did I miss one?
no matter.
the key to breaking up a procedure into 'chunks' is to accurately define the term 'long', as in 'takes a long time'. there are two broad classes of 'takes a long time':
the first class is exemplified by things that you're waiting for that are outside of your direct control. they might be quick or they might be slow, but you can't reliably predict which it will be. so you assume that they will potentially take a long time, and treat them as such. in these situations it's easy to decide where to break up the procedure. any place where you want to wait, you set your state variable to indicate what you're doing, and what you're waiting for, then you exit. for every possible event that you want to result in resuming your procedure, you write an event handler that invokes the next chunk *if* it sees the appropriate state value. the event handlers are the mechanism by which you fire your the next chunk.
the second class is exemplified by things that are within your control, yet simply take a long time to do. for example, calculating every possible route from holtburg to nanto might take a long time if there are a very large number of possible routes. in situations such as these, you can decide to only calculate, say, 100 routes. then you set your state variable to indicate that you're in the process of doing a route calculation, and that you've done 100 so far. then you exit. now, you need some kind of 'timed' event handler that will be invoked regularly. when that event handler is invoked, have it check to see if you're doing calculations. if so, it should call your calculation routine, telling it to skip the first 100 routes (because they're done already). it will do 100 more, then update the state variable to indicate that it's done 200 so far, etc. etc.
for the 'cast spell' example, you might have a state machine like so:
state 1: spell was cast, waiting for success/failure from server
state 2: received a success message from server
state 3: received a fizzle message from server
state 4: received an insufficient mana message from server
then you'd have a bunch of event handlers:
Success_Event:
if state = 1 then
state = 2
call whatever_you_want_to_do_after_spell_is_cast
end
Fizzle_Event:
if state = 1 then
state = 3
call whatever_you_want_to_do_for_fizzles
end
No_Mana_Event:
if state = 1 then
state = 4
call regain_mana_routine
end
of course, this example is basically worthless for actual use. manac checks could result in insufficient mana generating fizzle events, what to do after the spell is successful depends on why you cast it (was it the first of a series of buffs? the last in a series of buffs? a war spell?)
also, when you start fleshing out a real state machine, you may find yourself flooded with dozens or hundreds of states. what you do next after a successful cast of "harlune's blessing" may be very different from what you do next after casting "tusker's gift". to deal with this, you can implement *multiple* state machines. a 'strategy' state machine can keep track of high-level goals like 'buff', 'kill monster', 'loot', etc. a 'tactics' state machine can keep track of low-level goals like 'wait for spellcast result', 'wait for corpse to open', 'wait for item to be placed in inventory', etc. there was a discussion in the SkunkWorks forum recently on this topic (
http://forums.cameroncole.com/index.php?showtopic=1750).
-ken
DaMOB
Dec 17 2003, 04:03 PM
Ken, shouldn't those be in succession?
Success_Event:
if state = 1 then
state = 2
call whatever_you_want_to_do_after_spell_is_cast
end
Fizzle_Event:
if state = 1 then
state = 3
call whatever_you_want_to_do_for_fizzles
end
No_Mana_Event:
if state = 1 then
state = 4
call regain_mana_routine
end
Should actually be?
Success_Event:
if state = 1 then
state = 2
call whatever_you_want_to_do_after_spell_is_cast
end
Fizzle_Event:
if state = 2 then
state = 3
call whatever_you_want_to_do_for_fizzles
end
No_Mana_Event:
if state = 3 then
state = 4
call regain_mana_routine
end
If so I am slowly grasping this I think.
kgober
Dec 17 2003, 07:02 PM
what I meant was for each of those event handlers to be considered separately. when each clause is done, it's *done*.
it does *not* fall through to the next handler.
each event handler should look more like this:
Success_Event:
. if state = 1 then
. state = 2
. call whatever_you_want_to_do_after_spell_is_cast
. end
. exit handler
therefore, there is no restriction on the order in which they appear. they'll get called in whatever order the events occur in.
if it helps, think about your main program loop (the While 1=1 loop in an AC Tool macro) as looking more like this:
While 1 = 1
. if (_plugin_success_event_raised) then call success_event_handler
. if (_plugin_fizzle_event_raised) then call fizzle_event_handler
. if (_plugin_no_mana_event_raised) then call no_mana_event_handler
End
in a true state-machine-based program, your main loop looks exactly like that. it does nothing but wait for events ('input'). each state defines which events should cause a transition to new states. for example, a 'spell success' input would cause the 'casting buff spell' state to transition to the 'buff spell was successfully cast' state. it would also cause the 'casting war spell' state to transition to the 'war spell was successfully cast' state.
which of those two actually happens depends on what the current state was when the event was received. if it was 'buff', 'buff successful' will be next. if it was 'war', 'war successful' will be next. if it was neither (and no other state defines a transition for that event) then the event will simply be ignored (or an error raised, depending on how your state machine engine is set up).
-ken
-ken
DaMOB
Dec 17 2003, 08:42 PM
Well, this old dog is not learning this trick I am afraid.
Whats throwing me off is how everyone of those say if state = 1 then
but then it sets a different state = X but nothing worked off of it.
Thanks for trying to get me to grasp this concept though.
kgober
Dec 18 2003, 12:29 AM
well, we'll chip away at it bit by bit.
each of those handlers say 'if state = 1' because it's an incomplete example. it only shows the the transitions from state 1 to some other state (transitions being effected by setting 'state' to a new value). all of the transitions from all of the other states are *not* shown.
to see the big picture, you need to see the whole thing. or at least imagine that there are similar looking routines that define what to do in the *other* states. but seeing is easier than imagining.
here's a small snippet of code from SpikeBot.vbs (a SkunkWorks script):
| CODE |
' SkunkWorks will call this procedure after loading the script Sub Main() Dim s s = 0 ' start in state 0 (decide where to go next) Do Select Case (s) Case 0: s = Part0() ' decide where to go next Case 1: s = Part1() ' buy ingredients Case 2: s = Part2() ' make product Case 3: s = Part3() ' sell product Case 4: s = Part4() ' stack pyreals Case 5: s = Part5() ' buy plats/peas/etc. Case 6: s = Part6() ' log off End Select Loop Until (s = -1) ' exit script when state becomes -1 End Sub
' state 0 - decide where to go next Function Part0() Dim bu, n, k If (skapi.CitemOfKindInInv(ProdName) > 0) Then Call NavigateBowyer() ' if any product on-hand, go to bowyer Part0 = 3 ' and set next state to 'sell product' Else If (skapi.CitemOfKindInInv(Item1Name) > 0) And (skapi.CitemOfKindInInv(Item2Name) > 0) Then Call NavigateBowyer() ' if both ingredients on-hand, go to bowyer Part0 = 2 ' and set next state to 'make product' Else bu = 3 * skapi.burdenFull - skapi.burdenCur - 1 n = Min((bu - ProdBurden) \ (Item1Burden + Item2Burden), (bu - Item1Burden - Item2Burden) \ ProdBurden) k = skapi.cpyCash \ (Item1Cost + Item2Cost) If (Min(n, SlotsAvailable(MainPack) - 2) >= MinBatch) And (k >= 1) Then Call NavigateBowyer ' if you have room to make more, go to bowyer Part0 = 1 ' and set next state to 'buy ingredients' Else k = (skapi.cpyCash - Ceil(MinBatch * Item1Cost) - Ceil(MinBatch * Item2Cost)) \ PlatCost If (k > 0) Then Call NavigateArchmage() ' if no more room, but lots of cash, go to archmage Part0 = 5 ' and set next state to 'buy plats/peas/etc.' Else Call NavigateShutdown() ' if no more room, and no cash, go to shutdown point Part0 = 6 ' and set next state to 'log off' End If End If End If End If End Function |
there are also functions to handle each of the other major 'strategy' states. each Part<n> function has two tasks: accomplish its high-level goal, then decide which state should come next. some parts (1, 2, 3) proceed directly to the next state, others (4, 5) return to state 0 to let the Part0 state handler decide what to do next. Part6 sets the next state to -1 when it's done, to signal that the entire script should terminate.
in this example, the 'Part0' function is only concerned with what to do if you're currently in state 0. it's as if there were a big 'if state = 0' wrapped around it (due to the case statement in Main()).
from state 0, the possible transitions are to state 3, 2 or 1 (after navigating to the bowyer), or state 5 (after navigating to the archmage), or state 6 (after navigating to a safe shutdown location). in this example, state 0 is the *only* state that has multiple possible transitions. all other states always go to the same 'next' state when they're done.
it's a lot to wrap your head around, I know. and this isn't all that good an explanation. but as we talk, I get a better sense about where my explanations are falling short. so each try I should get a little better.
one thing that occurs to me is this: finite state machines and the need to avoid waiting loops are not directly related. you can have finite state machines that have waiting loops (SpikeBot falls into this category). you can also have programs that need to avoid waiting loops, yet do not use finite state machines (the 'Insane-Bot Client' Decal plugin falls into this category). so, in the near term, it might be best to forget about this no-waiting-loops business and just concentrate on the state machines themselves.
-ken
DaMOB
Dec 18 2003, 07:50 AM
Well, lets digest this a piece at a time. I don't know VB at all so I will try and make sense as best I can.
Sub Main()
Dim s
s = 0 ' start in state 0 (decide where to go next)
Do
Select Case (s)
Case 0: s = Part0() ' decide where to go next
Case 1: s = Part1() ' buy ingredients
Case 2: s = Part2() ' make product
Case 3: s = Part3() ' sell product
Case 4: s = Part4() ' stack pyreals
Case 5: s = Part5() ' buy plats/peas/etc.
Case 6: s = Part6() ' log off
End Select
Loop Until (s = -1) ' exit script when state becomes -1
End Sub
Somehere at some time I set S so the select case can act upon it BUT I never see S being set until after the fact with s = PartX().
In C I would (if I get this right):
| CODE |
switch (s) { case 0; decide where to go next; break; case 1; buy ingredients; break; case 2; make product; break; case 3; sell product; break; case 4; stack pyreals; break; case 5; buy plats/peas/etc.; break; case 6; log off; break; default: break; } |
Is that right? What is this -1 I see and could I case -1? I see it says loop until and thats a nasty wait I think but then again I do not know when or how -1 happens.
Ize
Dec 18 2003, 11:59 AM
Just thought I would throw in my 2 pyreals ... please keep the discussion going, because even though I'm not contributing to the discussion.... I'm still learning a great deal just through the dialogue.
Ize
kgober
Dec 18 2003, 12:59 PM
s is initialized to 0 just before the loop starts -- state 0 is the 'starting state'.
depending on what the current state is, one of six Part<n>() functions is called. each function performs its task, then returns what the next state should be. that value is placed in s, and the loop repeats.
if any of the Part<n>() functions returns -1, that is a signal that the loop should be terminated. state -1 is the 'ending state'.
here's a version of Main() in something that looks more like C++:
| CODE |
void main() { int s; s = 0 // start in state 0 while(s != -1) { // terminate when state becomes -1 switch(s) { case 0: s = Part0(); break; // decide where to go next case 1: s = Part1(); break; // buy ingredients case 2: s = Part2(); break; // make product case 3: s = Part3(); break; // sell product case 4: s = Part4(); break; // stack pyreals case 5: s = Part5(); break; // buy plats/peas/etc. case 6: s = Part6(); break; // log off } } } |
your observation that this program reeks of a waiting loop is correct. it would be possible to make a similar program that doesn't use a waiting loop, but its structure would be less clear. and since SkunkWorks scripts run in a different process than the AC client (just like AC Tool), waiting loops are less of an issue.
the WarBot SkunkWorks script is written in JScript, which bears a strong resemblance to C++. you might find it useful to have a look at it. unfortunately, it's a much larger and much more complex script.
-ken
DaMOB
Dec 18 2003, 01:24 PM
Well, could we not just get rid of the -1 and just do a default: break; in the switch (which I know encompasses everything not switched so -1 should be that case I think).
Sidenote. I am just plain lost with C++ and SW as I am running into the same stuff as I did in Decal and that is VB. I need a guide on what to do to get to a point that if you compiled it nothing would happen but it would be running then I could slowly add my C++ code to it. This is not about FSM but I just had to ask someone.
Lone
Dec 18 2003, 02:55 PM
This is not about FSM but in reply to your last post.
I am not sure, but I think this is what you are looking for. It is a step by step instruction on how to create (from scratch) a decal plugin using C++.
I know it works becuase this was what I used. I found it very helpful. It is broken down into steps, has you code a little, execute, code a little more and execute. It kind of shows you a little bit how things fit together and what does what.
http://homepage.ntlworld.com/k.de-silva/plugin1.html
Amaroth
Dec 18 2003, 06:01 PM
| QUOTE (Lone @ Dec 18 2003, 02:55 PM) |
This is not about FSM but in reply to your last post.
I am not sure, but I think this is what you are looking for. It is a step by step instruction on how to create (from scratch) a decal plugin using C++.
I know it works becuase this was what I used. I found it very helpful. It is broken down into steps, has you code a little, execute, code a little more and execute. It kind of shows you a little bit how things fit together and what does what.
http://homepage.ntlworld.com/k.de-silva/plugin1.html |
Thanks for the link. I'm assuming I use ALT Project since Microsoft Visual C++ .NET doesn't have ATL COM. I checked help for info and don't have a clear idea of the differences between ATL Project, ALT Server Project, and ALT Server Web Service...(options available) and COM.
Advice on options?
Lone
Dec 18 2003, 11:41 PM
Ameroth,
Sorry, I am not going to be much help with that. I am using VC++ 6.0. It does not have the .net stuff.
From what I read, the latest (.net) version should have a classic mode(Or at least have shipped with all the old libraries). Again, not sure how to get at it but I think all that stuff should have shipped with the latest version. You just may have to create it by hand if you can't find it in the wizard. (I can't find the article anymore)
Below is a link someone sent me to help me better understand COM if anyone is interested.
http://msdn.microsoft.com/library/default....guion020298.asp
kgober
Dec 19 2003, 12:41 AM
if you use "default: break;" that will just break out of the switch statement and re-enter the loop. the loop will run the switch statement again, which will break out again via the default: case, then loop again...
endless loop.
-ken
DaMOB
Dec 19 2003, 12:15 PM
I hadn't thought of that Ken but yep your right.
So, in one of my routines if something funky happens I set s = -1?
The more I look at that routine the less I am understanding.
| CODE |
void main() { int s; s = 0 // start in state 0 while(s != -1) { // terminate when state becomes -1 switch(s) { case 0: s = Part0(); break; // decide where to go next case 1: s = Part1(); break; // buy ingredients case 2: s = Part2(); break; // make product case 3: s = Part3(); break; // sell product case 4: s = Part4(); break; // stack pyreals case 5: s = Part5(); break; // buy plats/peas/etc. case 6: s = Part6(); break; // log off } } } |
That, to me, makes no sense because its just a huge loop waiting for the last routine to finish when I set s = -1. No control is ever thrown back into the lap of Decal so the game would choke. That is why I did the default break hoping it would leave and return to Decal and next fly around, after Decal did its thing, I would pick up where I left off and enter the next state.
kgober
Dec 19 2003, 02:27 PM
ah, I see your concern.
Each of those Part<n>() functions either returns immediately after doing something minor, or it pauses with a SkunkWorks WaitEvent() statement at appropriate times.
this is what I meant about FSMs and no-waiting-loops being two different things. because SkunkWorks runs in its own process space, it doesn't *need* to pause to let Decal process -- Windows takes care of it by allowing each process to have a slice of CPU time, round-robin (ignoring priority, I/O waits, etc.)
do you seek general understanding of FSMs, or is your primary concern how they may be applied in no-waiting-loops situations such as Decal plugins? because it is not strictly necessary to structure a plugin as an FSM, depending on what it does.
-ken
DaMOB
Dec 19 2003, 06:25 PM
Well, Ken I actually wish to understand the way Decal needs FSM to be done so I guess I need to grasp this FSM technique. Understanding of SW is even harder than Decal at this stage for me so I am trying to grasp it over SW (although I do have SW installed).
kgober
Dec 20 2003, 08:37 PM
ok, fair enough. I'll refrain from using SW-based examples. let me take a step back and review some of the basics for people who are new to this thread.
in the case of Decal plugins, you will often find yourself in a situation where you don't need to implement an actual FSM. but there are lessons to be learned from FSMs, and those *lessons* can profitably be applied to Decal plugins.
the first FSM lesson is that processing of a long task is split up into shorter tasks, and each of those shorter tasks is a 'state'. long tasks that can't possibly fail have a set of states that execute in a strict linear sequence. state 1 always leads to state 2, state 2 always leads to state 3, etc.
long tasks that might have several outcomes have a set of states that are arranged in a tree structure, where each leaf represents a possible outcome. so state 1 might lead to state 2 if it succeeds, but state 3 if it fails. state 2 might lead to state 4 if it succeeds, state 5 if it fails for one reason, and state 6 if it fails for another reason.
long tasks that potentially include repetition (such as casting a spell until it succeeds, or you run out of comps, or you run out of mana) have a set of states that are arranged in a DAG structure (google for 'directed acyclic graph' for more information).
a logical question then, is "what is a good way to split up a long task into states?"
first, you break up your task into steps. each step should be of one of the following three types:
1. do something immediately
2. wait for the occurrence of some event
3. branch to a different step based on the evaluation of some condition
now, look at your list of steps. anyplace that's a target of a 'branch' should start a new state. also, anywhere where you 'wait' should be a separate state. for example, suppose you have these steps:
| CODE |
1. do something 2. do something 3. do something 4. go to 5 if some condition is true, otherwise go to 8 5. do something 6. wait for something 7. go to 3 8. do something 9. go to 3 if some condition is true, otherwise end |
steps 5, 8 and 3 are targets of a branch. step 6 is a wait, so it gets a state of its own. you would end up with these states:
| CODE |
s1. doing (1, 2) s2. doing (3). if(condition) go to s3, else go to s5 s3. doing (5) s4. waiting for something (after which, go to s2) s5. doing (8), if(condition) go to s2 |
now, to translate this into code:
| CODE |
int state;
void do_task() { s = 1; // do 1 // do 2 s2(); }
void s2() { s = 2; // do 3 if(cond) s3(); else s5(); }
void s3() { s = 3; // do 5 s4(); }
void s4() { s = 4; }
void s5() { // do 8 if(condition) s2(); }
void some_event_handler() { if(s == 4) s2(); } |
now, this is example code, and as such it's kind of bad. it actually has a lot more functions that we really need, and it has a nasty tendency to use lots and lots of stack space, which only gets cleared out when entering s4() (or ending). but even if it's inefficient, it's at least educational.
as far as Decal coding is concerned, the part to look at is this: s4() is the 'waiting' state, so all the s4() function does is set the state variable to 4, and exit. at that point, all those stack frames we've accumulated will unwind, and control will eventually go back to Decal and AC.
however, at some point, Decal will call your event handler. when it does, you check to see if the current state is 4. if it is, you know that this is an event you're waiting for, so you proceed to call s2(), because that's what should happen when you're done waiting. if the current state is *not* 4, you can simply ignore the event. note that s2() is free to do as much processing as it likes, and all that processing will take place in the context of the event handler. when it's finally done (which happens when it either ends in s5, or finds its way back to s4 again) the call stack will unwind and control will return to the event handler, which can then exit, returning control to Decal and AC again.
the key is this: in a Decal plugin, *all* processing occurs as a result of some event happening. your plugin will do as much as it can, until it cannot proceed because it needs to wait for something to happen. at that point, it sets a variable that lets all of your event handlers know both what you're waiting for, and what you want to do if it happens. then, it exits.
at some point in the future, one of your event handlers will be invoked. if that handler sees from the state variable that you're waiting for its event, then it will do whatever processing is required in that case. otherwise, it will do nothing (because it knows you're waiting for something else instead).
this is a key difference from AC Tool, in which most things happen in your main code, with a relatively small amount of processing getting done in your event handlers. instead, in Decal, *everything* happens in the event handlers.
Decal programming is primarily event-driven, rather than FSM-driven. but FSM techniques can be applied to your event handlers to allow them to keep track of what you were doing, and what you plan to do next.
-ken
DaMOB
Jan 22 2004, 05:49 AM
My plugin is now at this stage of requirement so expect this thread to grow as I try and grasp this (OYE).

Ken or anyone. This is what I came up with but the only way I can get it to work is every 5 secs when the heartbeat is sent (age) from the server. There has to be a faster way but after looking its all I found.
| CODE |
void CPF::states() { switch (state) { case 0x01: getintocombatmode(4); break; case 0x02: checkcombatstate(4); break; case 0x03: checkcombatstate(1); break; default: break; } } void CPF::getintocombatmode(long temp) { m_pSite->Hooks->SetCombatState(4); state = 2; }
void CPF::getoutofcombatspellmode() { getintocombatmode(1); state = 3; }
void CPF::checkcombatstate(long temp) { if (m_pSite->Hooks->GetCombatState() != temp) state = 1; else state = 0; } |
edit: after looking even deeper I see I need to implement timers. OMG

is all I can say
Well, Lone was at this spot a year ago and I guess I will have to learn windows timer functions now too. Talk about massive learning because Decal is not one thing its like years of learning rolled into one neat little package but then I digress.
HWND hWnd;
then I add this to my initialize after all other things have been:
SetTimer(hWnd,1,300,NULL);
the destroy (raw_terminate) part I simply Kill the timer before I do anything else:
KillTimer(hWnd,1);
Now, how do I trigger off of the 300ms timer in Decal?
kgober
Jan 22 2004, 04:24 PM
I can't offer much help with timers.
But I can say this: you don't need to limit yourself to using the 5-second Age update messages to kick off your loop. *any* inbound or outbound message can be used to kick it off.
while there may be no single message that reliably comes in at a faster pace, there's always *some* kind of message coming in, unless you're idle in a very quiet area.
but you won't be idle very much if you're running a macro...
-ken
DaMOB
Jan 22 2004, 05:51 PM
I have litterally been at this for over 12 hours and I am completely lost on how to implement a timer in decal.
This much I know works: SetTimer((HWND__ *) m_pSite->GetHWND(), 1, 2000, NULL);
Same with KillTimer((HWND__ *) m_pSite->GetHWND(), 1);
but getting it to kick into any of my rountines I can say forget it. Very disgusted that something as simple as a windows timer has been this hard to implement.
Ipa
Jan 22 2004, 06:12 PM
| CODE |
SetTimer((HWND__ *) m_pSite->GetHWND(), 1, 2000, NULL);
|
Your 4th parameter is NULL.
If you want the timer event to be raised in your code, you need to pass a function address as the "callback".
The function whose address you provide must have a specific calling convention:
| CODE |
VOID CALLBACK MyTimerHandler(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime );
|
Assuming you choose "MyTimerHandler" as the name for your callback function, you then pass that to SetTimer:
| CODE |
| SetTimer((HWND__ *) m_pSite->GetHWND(), 1, 2000, MyTimerHandler); |
When the timer expires, Windows will call your function - it is up to you to decide what to do each time MyTimerHandler() is called.
Ipa
Jan 22 2004, 06:15 PM
Note, this is not Decal specific, it's standard Win32 API. If you haven't already, read the MSDN section on
Windows Timers for further background info and sample code.
DaMOB
Jan 22 2004, 07:34 PM
Already read that Ipa and much more. I tried using a callback and still it never worked so I tried it without and in a loop and it didn't work. I only posted the last snippet I was working on as I was leaving out the door when I posted that.
So apparently my first 10 hours of hacking I was actually right.

Hmmmm, I wonder why the original code never fired.
I will post my code in a second.

raw_initalize the very last thing I do is:
SetTimer((HWND__ *) m_pSite->GetHWND(), 1, 2000, TimerProce);
This is the only last piece that I could get to work:
static void CALLBACK TimerProce(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)
{
CPF * cpf;
cpf->statechecking();
}
very first thing I do in the raw_terminate:
KillTimer((HWND__ *) m_pSite->GetHWND(), 1);
in the .h file
class TimerProce
{
public:
void statechecking();
};
That class I read numerous times is how I am forced to do it but still it didn't work and since it is a seperate object from CPF I had to do the cpf->statechecking().
Now, before you ask

even if I simply do this:
SetTimer((HWND__ *) m_pSite->GetHWND(), 1, 2000, TimerProce);
void TimerProce(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)
{
...
}
nothing more or less than that and that doesn't even work.
Wish to see something VC++6 does that is just plain retarded?
error C2664: 'SetTimer' : cannot convert parameter 4 from 'void (__stdcall *)(struct HWND__ *,unsigned int,unsigned long,unsigned long)' to 'void (__stdcall *)(struct HWND__ *,unsigned int,unsigned int,unsigned long)'
Isn't that the same damn thing?
kgober
Jan 23 2004, 12:09 PM
error C2664: 'SetTimer' : cannot convert parameter 4 from 'void (__stdcall *)(struct HWND__ *,unsigned int,unsigned long,unsigned long)' to 'void (__stdcall *)(struct HWND__ *,unsigned int,unsigned int,unsigned long)'
to me, that error message is saying that parameter 4 is supposed to be one thing, but you're giving it another.
it's supposed to be a pointer to a void __stdcall function that accepts a struct HWND__ pointer, two unsigned ints, and an unsigned long.
you're giving it a pointer to a void __stdcall function that accepts a struct HWND__ pointer, an unsigned int, and two unsigned longs.
or vice-versa, i'm kinda hazy on this.
C will auto-coerce your arguments as necessary at compile-time. Windows will not do so at run-time, at run-time the argument types have to be exactly correct. and since the callback happens at run-time...
-ken
Ipa
Jan 23 2004, 12:33 PM
| CODE |
raw_initalize the very last thing I do is: SetTimer((HWND__ *) m_pSite->GetHWND(), 1, 2000, TimerProce);
|
SetTimer has a return value which is being discarded. MSDN quote:
| QUOTE |
If the function fails to create a timer, the return value is zero. To get extended error information, call GetLastError.
|
Maybe there's a clue to the problem if you capture and check the return code ?
And:
| CODE |
static void CALLBACK TimerProce(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime) { CPF * cpf; cpf->statechecking(); } |
cpf is an unitialized pointer, and you'll crash if you try to call ->statechecking() on it.
DaMOB
Jan 23 2004, 12:34 PM
Very good eye you have Ken because I completely never saw the two longs.
Lets just say I am making 0 headway with timers. I did get a timer to return my address but only if I told it to not use the window returned via decal. If I tell it to use the window then all sorts of nasty compilation errors happen.
Right now Ipa I am trying everything and anything because its just not happening for timers.
Where I am at right now is I have done this
class CPG
{
public:
static void CALLBACK CPG::Ticker(HWND hWnd, UINT nMsg, UINT nIDEvent, DWORD dwTime);
};
int c = ::SetTimer(NULL,1, 1000, (TIMERPROC) CPG::Ticker);
Now c returns a 16 bit value so must be working BUT I can't get this part to work to make sure.
void CALLBACK CPG::Ticker(HWND hWnd, UINT nMsg, UINT nIDEvent, DWORD dwTime)
{
CPF::statechecking();
}
error (c vs c++) error C2352: 'CPF::statechecking' : illegal call of non-static member function.
Ipa
Jan 23 2004, 01:38 PM
| QUOTE |
| error (c vs c++) error C2352: 'CPF::statechecking' : illegal call of non-static member function. |
OK, we're getting deeper into C++ now - very little of this is Decal related.
The keyword here is static.
A static member function of a class can be called without an instance of the class existing (that's how Windows can call your callback, without having a this pointer on which to call it).
So far so good. The problem with a static member function is that you can't reference this inside it.
Remember, this is just a pointer to the current class instance - but a static member function can be called without an instance, therefore, this is not guaranteed to exist.
There are several solutions to this, depending on the design pattern. 1 involves using the Windows MM (multi-media) timers, whic are like normal timers, except they allow you to pass extra user-defined params into the callback. If you convert to this technique, you'd pass this as the user-defined param, so it would be available within the callback.
An easier approach exists if you are using the singleton design pattern. If there is only ever 1 and only 1 instance of the CPF class, you can add a static member variable to it, which you assign this to inside the class constructor.
What follows below is off the top of my head, and may have errors - it's for concept only ...
In your class header:
| CODE |
Class CPF { public: CPF::CPF(); // A static member variable, to point to "this" static CPF * m_pActiveClass; // Your callback static void CALLBACK CPG::Ticker(HWND hWnd, UINT nMsg, UINT nIDEvent, DWORD dwTime);
private: //rest of class, including your stateChecking() method etc };
|
In your cpp file:
| CODE |
CPF * CPF::m_pActiveClass= NULL; // init the pointer
// constructor CPF::CPF() { m_pActiveClass = this; // Make a copy of 'this' }
// destructor CPF::~CPF() { m_pActiveClass = NULL; // }
// STATIC member function for callback void CALLBACK CPF::Ticker(HWND hWnd, UINT nMsg, UINT nIDEvent, DWORD dwTime) { //You CAN'T do this, because statechecking() is non-static //CPF::statechecking();
//You CAN do this, because m_pActiveClass IS static, and //points to a valid class instance m_pActiveClass->stateChecking(); }
|
DaMOB
Jan 23 2004, 01:52 PM
Yes, that is what I did (ALMOST) but statechecking is a member of CPF which is the main program. The CPG class holds only one method and that is the timer routine that is called however often I set it via Settimer.
class CPG
{
public:
CPF::CPF();
// A static member variable, to point to "this"
static CPF * m_pActiveClass;
// Your callback
static void CALLBACK Ticker(HWND hWnd, UINT nMsg, UINT nIDEvent, DWORD dwTime);
private:
//rest of class, including your stateChecking() method etc
};
Of course that gives the 2 errors about the membership (error C2838: illegal qualified name in member declaration) but CPG is as I said.
So I //CPF::CPF(); and BOOM:
error LNK2001: unresolved external symbol "public: static class CPF * CPG::m_pActiveClass" (?m_pActiveClass@CPG@@2PAVCPF@@A)Release/PF.dll : fatal error LNK1120: 1 unresolved externals.
Finally I was able to get this to compile. I had to add the static CPF part like so:
void CALLBACK CPG::Ticker(HWND hWnd, UINT nMsg, UINT nIDEvent, DWORD dwTime)
{
static CPF * m_pActiveClass;
m_pActiveClass->ChatOut("Fired");
}
here is the next part:
int c = SetTimer((HWND__ *)m_pSite->HWND,1, 1000, (TIMERPROC) CPG::Ticker);
DWORD dw = GetLastError();
c = 1, dw = 0
so it worked but not once did it do anything.
DaMOB
Jan 23 2004, 04:09 PM
I guess this is an update but I think it is half working and I will tell you why.
Change the 1000 to 3000 and add this in the Ticker:
MSG msg;
::GetMessage(&msg, hWnd, NULL, NULL);
static CPF * m_pActiveClass;
m_pActiveClass->ChatOut(CString("Fired")); //never does crap
::DispatchMessage(&msg);
and every 3 secs AC locks up until I move my mouse. So I see the routine is getting called but I am never going to ChatOut (which is a simple writetoscreen). If I am going and its another issue I have no idea either because I can't track it BUT at least I know the timer function is getting called but nothing I can do with it as it is.
Ipa
Jan 23 2004, 04:59 PM
| CODE |
static CPF * m_pActiveClass; m_pActiveClass->ChatOut(CString("Fired")); //never does crap
|
That should blow up.
You're declaring a pointer to a class and calling a class method without initializing the pointer to point to anything valid.
*Confused*
You have CPF and CPG classes ? or just CPF mistyped as CPG ?
DaMOB
Jan 23 2004, 05:02 PM
I have 2 classes.
CPF is the main class where everything is stored except Ticker and Ticker lives in the other Class CPG.
Ipa
Jan 23 2004, 05:13 PM
| QUOTE |
| CPF is the main class where everything is stored except Ticker and Ticker lives in the other Class CPG. |
In that case, the code I gave above won't work.
It's designed for a single class, with a singleton instance, where the class maintains an internal static pointer to the 1 instance of itself, which is accessible from its static callback.
DaMOB
Jan 23 2004, 05:37 PM
You would think this would be easier to do than this. Now the last step in getting a timer to work is getting Ticker to be able to call any NON-static function inside the other class. I can fake out the compiler and it compiles fine but never really calls any function inside CPF.
edit: I added a beep command and it is working so now if someone can tell me how to call a function in another class it will all fall together (and no CPF::xxxx() will not work).
Ipa
Jan 23 2004, 06:09 PM
Why does your timer callback have to be in a class byitself, if all you want it to do is call member functions in a different class ?
DaMOB
Jan 23 2004, 08:37 PM
From all I read they said it had to be because it has to be in a class unto itself so that it can be declared a static CALLBACK to get it to work.
I have since removed CPG.
void CALLBACK CPF::Ticker(HWND hWnd, UINT nMsg, UINT nIDEvent, DWORD dwTime)
{
static CPF * m_pActiveClass;
// m_pActiveClass->ChatOut("Fired!");
Beep(1000,50);
}
I get a beep!
void CALLBACK CPF::Ticker(HWND hWnd, UINT nMsg, UINT nIDEvent, DWORD dwTime)
{
static CPF * m_pActiveClass;
m_pActiveClass->ChatOut("Fired!");
Beep(1000,50);
}
I get nothing!
Ipa
Jan 23 2004, 09:10 PM
Scroll back up to my code fragment.
You're missing the most important piece - where m_pActiveClass gets initialized to a valid pointer in the calss constructor:
| CODE |
// constructor CPF::CPF() { m_pActiveClass = this; // Make a copy of 'this' }
|
There's a reason the code below isn't working:
| CODE |
void CALLBACK CPF::Ticker(HWND hWnd, UINT nMsg, UINT nIDEvent, DWORD dwTime) { static CPF * m_pActiveClass; m_pActiveClass->ChatOut("Fired!"); Beep(1000,50); }
|
In your code, m_pActiveClass is declared - you have thrown 4 bytes on the stack, called it a pointer but pointed it to ... nothing.
DaMOB
Jan 23 2004, 09:25 PM
yeah, the "this" thing blows my mind (even after reading about it all day).
Where do I put
// constructor
CPF::CPF()
{
m_pActiveClass = this; // Make a copy of 'this'
}
at? I tried in the .h file where the class is defined but msvc bitched and moaned.
Here is what my header looks like:
| CODE |
///////////////////////////////////////////////////////////////////////////// // CPF class ATL_NO_VTABLE CPF:public IEchoEventsImpl2<EVT_ECHO, CPF>,// Server message events public IViewEventsImpl<EVT_VIEW, CPF>,// View Events public ICommandEventsImpl<CB_CHECK1, CPF>,public DecalPlugins::IPluginSink,public DecalPlugins::IPlugin,public CComObjectRootEx<CComSingleThreadModel>,public CComCoClass<CPF, &CLSID_PF>,public IDispatchImpl<IPF, &IID_IPF, &LIBID_PFPLGLib> { public: CPF() : m_Me(0), m_IsActive(false) { }
DECLARE_REGISTRY_RESOURCEID(IDR_PF)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CPF) COM_INTERFACE_ENTRY(IPF) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY(DecalPlugins::IPlugin) COM_INTERFACE_ENTRY(DecalPlugins::IPluginSink) END_COM_MAP()
BEGIN_SINK_MAP(CPF) SINK_ENTRY_EX(EVT_ECHO, __uuidof(DecalFilters::IEchoSink2), DISPID_SERVERMSG, onServerMessage) SINK_ENTRY_EX(EVT_VIEW, __uuidof(DecalPlugins::IViewEvents), DISPID_ACTIVATE, onActivate) SINK_ENTRY_EX(EVT_VIEW, __uuidof(DecalPlugins::IViewEvents), DISPID_DEACTIVATE, onDeactivate) SINK_ENTRY_EX(EVT_VIEW, __uuidof(DecalPlugins::IViewEvents), DISPID_SIZE, onSize) SINK_ENTRY_EX(EVT_VIEW, __uuidof(DecalPlugins::IViewEvents), DISPID_SIZE, onSizing) END_SINK_MAP()
// IPF public: blah, blah, blah...... |
DaMOB
Jan 24 2004, 08:58 AM
I now have a timer and it calls my statechecking. I relooked over 30-40 links on google and then pulled out my C++ reference book and nothing quite clicked but just enough entered my brain to do this.
The header file.
| CODE |
///////////////////////////////////////////////////////////////////////////// // CPF class ATL_NO_VTABLE CPF:public IEchoEventsImpl2<EVT_ECHO, CPF>,// Server message events public IViewEventsImpl<EVT_VIEW, CPF>,// View Events public ICommandEventsImpl<CB_CHECK1, CPF>,public DecalPlugins::IPluginSink,public DecalPlugins::IPlugin,public CComObjectRootEx<CComSingleThreadModel>,public CComCoClass<CPF, &CLSID_PF>,public IDispatchImpl<IPF, &IID_IPF, &LIBID_PFPLGLib> { public: CPF() : m_Me(0), m_IsActive(false) { static CPF* m_pActiveClass; }
DECLARE_REGISTRY_RESOURCEID(IDR_PF) DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CPF) COM_INTERFACE_ENTRY(IPF) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY(DecalPlugins::IPlugin) COM_INTERFACE_ENTRY(DecalPlugins::IPluginSink) END_COM_MAP()
BEGIN_SINK_MAP(CPF) SINK_ENTRY_EX(EVT_ECHO, __uuidof(DecalFilters::IEchoSink2), DISPID_SERVERMSG, onServerMessage) SINK_ENTRY_EX(EVT_VIEW, __uuidof(DecalPlugins::IViewEvents), DISPID_ACTIVATE, onActivate) SINK_ENTRY_EX(EVT_VIEW, __uuidof(DecalPlugins::IViewEvents), DISPID_DEACTIVATE, onDeactivate) SINK_ENTRY_EX(EVT_VIEW, __uuidof(DecalPlugins::IViewEvents), DISPID_SIZE, onSize) SINK_ENTRY_EX(EVT_VIEW, __uuidof(DecalPlugins::IViewEvents), DISPID_SIZE, onSizing) END_SINK_MAP()
// IPF public: |
the main file I added:
| CODE |
| CPF* m_pActiveClass = NULL; //this added where I declare and initialize all my global variables. |
| CODE |
| m_pActiveClass = this; //added this to the CPF Decal initialize variables routine. |
Finally, in the timer routine I added what I already knew about:
| CODE |
void CALLBACK CPF::Ticker(HWND hWnd, UINT nMsg, UINT nIDEvent, DWORD dwTime) { m_pActiveClass->statechecking(); Beep(1000, 50); } |
I did not add a deconstructor or a constructor because I do not know if they are needed and looking in my book I still do not know where they go or how they are used.
Chazcon
Jun 15 2006, 02:33 PM
Fascinating thread.
Ipa
Jun 15 2006, 05:12 PM
After much searching, I finally found out the
real truth behind FSM
DaMOB
Jun 15 2006, 05:49 PM
Flashback from hell for me.
FSM is the devil....it is EVIL!
Chazcon
Jun 15 2006, 06:24 PM
ROFL!
WWFSMD ??
loool too funny!
Chazcon
Dec 10 2007, 07:24 PM
15 minutes to kill before my ass is outta here and I can go home to code...
Reading some old threads here and have a comment:
I use FSM in any macro that requires you to keep track of where the character is in the process, in the event of a crash, error, or lag spike. I set up a dataset named Control and create a field named State. Key processes look something like this:
CODE
If Control[State] < 14
// do stuff here
Call 700ControlChange NewState, 14
The call is to a Procedure...Using that edits dataset Control and changes the state field of course. In this way if I get an error that requires a restart, the macro 'knows' where the character is in the process and picks up right where it left off. At the end of the cycle set State = 0 and login the next character. Very useful, and it sounds so simple and obvious now, although I will admit it took some effort to get my mind wrapped around it. This old thread is what caused the spark in me to learn FSM and I thank you all for it.
DaMOB
Dec 10 2007, 10:44 PM
My latest release was ALL FSM in C++. Not hard but it does take getting used to and thank the devil or whatever for switch and case statements because they make FSM 1000% easier.