[Tutorial] State machines (automata)
#1

State machines (automata)


Disclaimer

This tutorial is apart of ******' Code Optimisation topic which is no longer available - I do not take credit for it.

Contents
  • Contents
  • Introduction
  • Example 1
  • Advantages
  • Example 2
  • Examination
  • Explanation
    • state
    • Functions
      • No states
      • One state
      • Multiple states
      • Other states
    • entry()
    • Note
  • Example 3
  • Further information
Introduction

This is something that I have NEVER seen used in ANY released script, which is a shame really (though I too am guilty of not using them) as they are very useful. PAWN natively supports a system called state machines (also known as automata), which basically allow you to define multiple copies of the same function to use in different circumstances.

Example 1

To demonstrate this system I have developed a very simple example mode:

Using standard techniques, so people will recognise what it is that this mode should do:

Код:
#include <a_samp>

forward change();

enum
{
	end = 0,
	gmx
}

new
	gExitType;

main()
{
	gExitType = end;
	SetTimer("change", 10000, 0);
}

public OnGameModeExit()
{
	switch (gExitType)
	{
		case end:
			printf("This mode was ended by the server");
		case gmx:
			printf("This mode was ended by the timer");
	}
}

public change()
{
	gExitType = gmx;
	GameModeExit();
}
Using automata, making the system far simpler, and with native implementation speed:

Код:
#include <a_samp>

forward change();

main()
{
	state exitType:end;
	SetTimer("change", 10000, 0);
}

public OnGameModeExit() <exitType:end>
{
	printf("This mode was ended by the server");
}

public OnGameModeExit() <exitType:gmx>
{
	printf("This mode was ended by the timer");
}

public change()
{
	state exitType:gmx;
	GameModeExit();
}
Advantages

So what are the advantages to using this technique? Well, as you can clearly see the code is smaller; granted I added an enum to the first version to make the examples more similar but the OnGameModeExit functions are definately smaller. It's also alot more explicit - you don't need large switch statements in functions to determine what's going on, if you wanted you could put the different OnGameModeInits in different files to handle different sets of code independently. It also uses less global variables.

Example 2

Here's another handy example, this time using a debug command to quickly turn printing on and off:

Код:
#include <a_samp>

dprint(message[]) <printState:on>
{
	// Display the message
	print(message);
}

dprint(message[]) <printState:off>
{
	// Do nothing
	#pragma unused message
}

forward output();

main()
{
	state printState:off;
	// Turn on the example timer
	SetTimer("output", 1000, 1);
}

public OnPlayerCommandText(playerid, cmdtext[]) <printState:on>
{
	state (!strcmp(cmdtext, "/debug")) printState:off;
}

public OnPlayerCommandText(playerid, cmdtext[]) <printState:off>
{
	state (!strcmp(cmdtext, "/debug")) printState:on;
}

public output()
{
	static
		sCount = 0;
	if (++sCount == 5)
	{
		OnPlayerCommandText(0, "/debug");
		sCount = 0;
	}
	// Just constantly print a message here as an example.
	dprint("Hello World");
}
Examination

A few important things to note here:

Firstly, dprint is not a public function, it's just a regular function, but states still work on it.

Secondly the "state" directive can have conditionals in it. This line:

Код:
state (!strcmp(cmdtext, "/debug")) printState:on;
Is equivalent to:

Код:
if (!strcmp(cmdtext, "/debug"))
{
	state printState:on;
}
Thirdly there are two different functions (OnPlayerCommandText and dprint) both with alternate versions relying on the same state automata and this is perfectly fine.

Explanation

Now we've looked at some examples you're probably wondering what's going on (unless, of course, you've read pawn-lang.pdf), so let's look at the code closer:
  • state
Код:
state a:b
That puts the automata "a" into state "b" (there is an annonymous state but I'd not recommend using it, so if you want to find out about it go read page 37 on of pawn-lang.pdf). You don't need to declare states in advance like variables, they must be declared in functions (all the ones in the examples above are initialised in "main") but they are implicitly always global (they have to be or they wouldn't work on functions).

As mentioned above you can have conditionals in state directives:

Код:
state (c == 1) a:b;
That will put automata "a" in state "b" only when "c" is equal to 1. You can of course use the standard if/else constructs.
  • Functions
You can declare multiple copies of the same function to be called under different circumstances, with no need for checks either around the call or within the function itself (these different circumstances are called states). A function can have no states, one state, multiple states or all other states. For example:
  • No states
Код:
dprint(message[])
{
	print(message);
}
This function is always called, regardless of the state of any automata.
  • One state
Код:
dprint(message[]) <debugState:on>
{
	print(message);
}
This function is called if "debugState" is in the state "on".
  • Multiple states
Код:
dprint(message[]) <debugLevel:_1, debugLevel:_2>
{
	print(message);
}
This function is called if "debugLevel" is _1 or _2 (because symbols can't start with numbers).
  • Other states
Код:
dprint(message[]) <>
{
	print(message);
}
This function is called if no other dprint function would be called (e.g. if "debugLevel" does not match any of the explicit values given for any of the other dprint functions).
  • entry()
This is a special function (like "main") which is called the moment you go into a state, unfortunately it only works for a single automata.

Код:
main ()
{
	printf("hello");
	state myState:moo;
	printf("world");
}

entry() <myState:moo>
{
	printf("change");
}
Will output:

Код:
hello
change
world
  • Note

There is one firly major problem with using this in SA:MP - alot of what happens is player dependant, so one player could be doing one thing while another is doing another. You can only have functions tied to a single automata, so generally these would be used for global systems rather than anything which varies per player, but for those things they're fine.

Example 3

Lets look at one final example just to get this all straight. This time we'll look at a race written using automata, this is a very basic outline and some functions are missing, but shows how you would use this system to simplify the amount of code run. Although you still need checks to deal with the multiple player situation (otherwise you could wait till someone went through the second to last checkpoint then go through ANY checkpoint and win) this simplifies the amount of code run at any given time. If you know no-one is going for the final checkpoint why bother to see if they've got it? It's just a waste of time!

Код:
new
	// Number of people in the race
	gEntrants = 0;

main()
{
	state raceState:none;
}

StartRace()
{
	// Start the race.
	state raceState:start; // No explicit handle, done in <>.
}

CancelRace()
{
	state raceState:none; // Reset the race.
	gEntrants = 0;
}

public OnPlayerEnterRaceCheckpoint(playerid) <raceState:none>
{
	// Called when there's no race on.
	SendClientMessage(playerid, 0xFF0000AA, "Sorry, there's no race on at this time");
}

public OnPlayerEnterRaceCheckpoint(playerid) <raceState:finish>
{
	// Called when someone is going for the last checkpoint.
	if (AtLastCheckpoint(playerid))
	{
		// This person got to the last checkpoint first.
		SendClientMessage(playerid, 0xFF0000AA, "Congratulations, you won the race");
		DisablePlayerRaceCheckpoint(playerid);
		state raceState:won;
		--gEntrants;
	}
	else
	{
		// Not there yet.
		SetNextCheckpoint(playerid);
	}
}

public OnPlayerEnterRaceCheckpoint(playerid) <raceState:won>
{
	// Called when someone has already won but others are still going.
	if (AtLastCheckpoint(playerid))
	{
		// Reached the end but didn't win.
		SendClientMessage(playerid, 0xFF0000AA, "Sorry, somebody already won");
		DisablePlayerRaceCheckpoint(playerid);
		state (--gEntrants == 0) raceState:none;
	}
	else
	{
		// Not there yet.
		SetNextCheckpoint(playerid);
	}
}

public OnPlayerEnterRaceCheckpoint(playerid) <>
{
	// Called during the race, just set their next checkpoint.
	SetNextCheckpoint(playerid);
	if (AtLastCheckpoint(playerid))
	{
		// Someone is going for the finish line.
		state raceState:finish;
	}
}
Further information

For more information and a more detailed explanation see page 37 on in pawn-lang.pdf.
Reply
#2

Yes, this topic I did mis already. Maybe you ought to add a notice of the original author of this tutorial.
Reply
#3

A good lesson. You could write about the the directives? #include #pragma #define #if #endif etc

Add:

already I found https://sampforum.blast.hk/showthread.php?tid=570948
Reply
#4

That's very hard to find this topic. every i type "state samp" in ****** search engine, it give me wrong result, thanks for repost, i really need it alot.


#Gonna leave this reply here to make me easy to find this thread later.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)