Help - Search - Members - Calendar
Full Version: Trying to understand FSM
AC Tools Everything Macro > General Discussion > C++/Delphi/VB Development
DaMOB
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
My plugin is now at this stage of requirement so expect this thread to grow as I try and grasp this (OYE). blink.gif

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 dry.gif is all I can say laugh.gif

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
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
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. dry.gif
Ipa

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
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
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. dry.gif Hmmmm, I wonder why the original code never fired.

I will post my code in a second. smile.gif

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 smile.gif 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
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
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
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
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
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
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
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
I have 2 classes.

CPF is the main class where everything is stored except Ticker and Ticker lives in the other Class CPG.
Ipa
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
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
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
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
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
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
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
Fascinating thread.
Ipa
After much searching, I finally found out the real truth behind FSM
DaMOB
Flashback from hell for me.

FSM is the devil....it is EVIL!
Chazcon
ROFL!

WWFSMD ??


loool too funny!
Chazcon
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
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.
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Invision Power Board © 2001-2010 Invision Power Services, Inc.