64
The Polling Problem Speaker Name Dino Dini NHTV University of Applied Sciences

The polling problem

Embed Size (px)

DESCRIPTION

GDC 2013 AI summit talk

Citation preview

Page 1: The polling problem

The Polling Problem

Speaker NameDino Dini NHTV University of Applied Sciences

Page 2: The polling problem

A bit about me

I have been designing and programming video games for over 30 years.

I am originally from the UK, live in the Netherlands and now teach Video Game Programming at NHTV in Breda (IGAD).

I am quite well known in the VG industry outside of the US for making a series of games concerned with an obscure sport known as Football, not to be confused with the US popular sport "Handegg".

Page 3: The polling problem

The focus of this talk

A = Autonomous

I = Interactive

Computers and their languages are generally good for A and less good for I.

My focus is solving the problem at the programming language level, rather than through the creation of virtual machine architectures (such as Behaviour Trees).

Page 4: The polling problem

Roman Numbers

The Romans had no symbol for 0.

The result was that it held back their development of technology and science.

Page 5: The polling problem

Doing Nothing

Do we lack an appropriate representation in computer science for doing nothing?

Page 6: The polling problem

Turing's Vision

Turing was a mathematician, and was interested in computers as a device for solving problems.

Mathematically, there was no concept of doing nothing.

Page 7: The polling problem

Data In, Data Out

The program runs. The program terminates, all might be well.

The program runs. The program never terminates. Disaster.

If the program terminates before it has finished its job, that's an aberration.

Why on earth should one want to stop the program, apart from being too impatient to wait?

Doing nothing makes no sense; simply do not run the program.

IMMUTABLE

INPUT DATACOMPUTER ALGORITHM

IMMUTABLE OUTPUT DATA

Page 8: The polling problem

It's a function, Jim

Computers were developed to process data in the manner of a function.

We would like these to execute in zero time.

Is the temporal nature of computation merely an inconvenience?

Page 9: The polling problem

The interactive world is ongoing

The interactive world does not fit this model well.

Tasks take time and need to pass information between each other asynchronously.

Computations must contend with mutable input data.

Page 10: The polling problem

Mutable input data

We normally do not consider the possibility that the input data could change while we perform computations on it...

What is the square root of 345 ?

OK working on it...

Wait, sorry I meant 346...

Nearly there...

!@@#$@!!

Page 11: The polling problem

Mutable input data

This is unfortunately common in the real world, and a necessity in effective game AI.

My GPS loves to say "Recalculating..."

Page 12: The polling problem

The Polling Problem

Q: What do you get when you try to process unpredictable and time dependent input with a Turing machine?

Page 13: The polling problem

The Polling Problem

Q: What do you get when you try to process unpredictable and time dependent input with a Turing machine?

A: A polling loop

While(!EscapePressed()) { // ugh, there must be a better way

if(pathPlanner->ExecSingleStep()) {

break;

}

}

Page 14: The polling problem

Say "When"

While(true) {

if(!GlassAcceptablyFull()) {

// erm... do nothing???

yield();

} else {

Say("When!");

break;

}

}

Page 15: The polling problem

Say "When"

While(true) {

if(!GlassAcceptablyFull()) {

// erm... do nothing???

yield();

} else {

Say("When!");

break;

}

}

Page 16: The polling problem

Say "When"

While(true) {

if(!GlassAcceptablyFull() &&

GetGlassStatus() == Normal) {

// erm... do nothing???

yield();

} else {

Say("When!");

break;

}

}

Page 17: The polling problem

Say "When"

// Pourer

While(true) {

if(!Heard("When!")) {

KeepPouring();

yield();

} else {

StopPouring();

}

}

Page 18: The polling problem

Say "When"

// Pourer

While(true) {

if(!Heard("When!")) {

KeepPouring();

yield();

} else {

StopPouring();

}

}

Page 19: The polling problem

Say "When"

// Pourer

While(true) {

if(!Heard("When!")) {

KeepPouring();

yield();

} else {

StopPouring();

}

}

Page 20: The polling problem

Say "When"

// Pourer

While(true) {

if(!Heard("When!") && !GlassFull()) {

KeepPouring();

yield();

} else {

StopPouring();

}

}

Page 21: The polling problem

If and When

If● A change in state

● Dependent on current state

● When the If is executed

When● A change in state

● Dependent on resulting state

● When a specified state change occurs

Page 22: The polling problem

When?

Computers are not really designed for When. They are designed around procedural logic, effectively a series of ifs executed in a sequence.

When is actually a more natural way to express responses to time dependent input.

Page 23: The polling problem

When you have Messages

The typical solution is to use messages, but this does not directly solve the polling problem.

Page 24: The polling problem

"When you get the message, do something else"

// pseudocode

function WaitForGlassFill() {

messageHandler = new Handler(OnGlassAcceptablyFull,

delegate(Message m) {

// code executed on message

// Exit from doing nothing

// But how?

});

SetAnimationState("ObserveFilling");

while(true) { // do nothing

Yield();

}

}

Page 25: The polling problem

You have to keep checking for data changed by message handlers// pseudocode

function WaitForGlassFill() {

int onMessage=0;

bool stopFlag;

messageHandler = new Handler(eOnGlassAcceptablyFull,

delegate(Message m) {

stopFlag = true;

onMessage = eOnGlassAcceptablyFull;

});

SetAnimationState("ObserveFilling");

while(!stopFlag) { // do nothing

Yield();

}

switch(onMessage) { // ..etc..

Page 26: The polling problem

You have to keep checking for data changed by message handlers// pseudocode

function WaitForGlassFill() {

int onMessage=0;

bool stopFlag;

messageHandler = new Handler(eOnGlassAcceptablyFull,

delegate(Message m) {

stopFlag = true;

onMessage = eOnGlassAcceptablyFull;

});

SetAnimationState("ObserveFilling");

while(!stopFlag) { // do nothing

Yield();

}

switch(onMessage) { // ..etc..

Hello, Polling

Page 27: The polling problem

I wish I could simply say...

function WaitForGlassFill() {

SetAnimationState("ObserveFilling");

When(ReceivedMessage(eOnGlassAcceptablyFull)) {

SayWhen();

StartNormalAnimationState();

End; // this will stop this coroutine

}

DoNothing(); // until coroutine terminated

}

Page 28: The polling problem

I wish I could simply say...

function WaitForGlassFill() {

SetAnimationState("ObserveFilling");

When(ReceivedMessage(eOnGlassAcceptablyFull)) {

SayWhen();

StartNormalAnimationState();

End; // this will stop this coroutine

}

DoNothing(); // until coroutine terminated

}

Here I have a trivial case, but maybe I want some other tasks going on here too, like watching out for a sniper.

Page 29: The polling problem

I want reusable behaviours

// anything can pause, it's a substate

// needs to be interruptible!

function Pause(int _count) {

int count=_count;

while(count-- > 0) {

yield;

}

end;

}

Page 30: The polling problem

I want reusable behaviours

function WaitForGlassFill() {

SetAnimationState("ObserveFilling");

When(ReceivedMessage(eOnGlassAcceptablyFull)) {

SayWhen();

StartNormalAnimationState();

end;

}

while(true) {

Pause(Random(25,50));

GlanceAtRandomPointOfInterest();

yield;

}

}

Page 31: The polling problem

I want reusable behaviours

function WaitForGlassFill() {

SetAnimationState("ObserveFilling");

When(ReceivedMessage(eOnGlassAcceptablyFull)) {

SayWhen();

StartNormalAnimationState();

end;

}

while(true) {

Pause(Random(25,50));

GlanceAtRandomPointOfInterest();

yield;

}

}

This needs to be interuptable when WaitForGlassFill handles a message and switches task.

Page 32: The polling problem

I want reusable behaviours

function WaitForGlassFill() {

SetAnimationState("ObserveFilling");

When(ReceivedMessage(eOnGlassAcceptablyFull)) {

SayWhen();

StartNormalAnimationState();

end;

}

while(true) {

Pause(Random(25,50));

GlanceAtRandomPointOfInterest();

yield;

}

}

And what if we want to do something that is not instantaneous?

Page 33: The polling problem

I want reusable behaviours

function WaitForGlassFill() {

SetAnimationState("ObserveFilling");

When(ReceivedMessage(eOnGlassAcceptablyFull)) {

SayWhen();

Pause(50);

StartNormalAnimationState();

end;

}

while(true) {

Pause(Random(25,50));

GlanceAtRandomPointOfInterest();

yield;

}

}

Page 34: The polling problem

I want reusable behaviours

function WaitForGlassFill() {

SetAnimationState("ObserveFilling");

When(ReceivedMessage(eOnGlassAcceptablyFull)) {

SayWhen();

Pause(50);

StartNormalAnimationState();

end;

}

while(true) {

Pause(Random(25,50));

GlanceAtRandomPointOfInterest();

yield;

}

}

No! Can't do this. It would cause the calling object to Pause (at best).

Page 35: The polling problem

Messages must change the "program counter"

function WaitForGlassFill() {

SetAnimationState("ObserveFilling");

When(ReceivedMessage(eOnGlassAcceptablyFull)) {

SwitchTo(GlassFullEnough);

}

State(Normal):

while(true) {

Pause(Random(25,50));

GlanceAtRandomPointOfInterest();

yield;

}

State(GlassFullEnough):

Pause(50); // delay before responding

// etc

}

Page 36: The polling problem

Messages must change the "program counter"

function WaitForGlassFill() {

SetAnimationState("ObserveFilling");

When(ReceivedMessage(eOnGlassAcceptablyFull)) {

SwitchTo(GlassFullEnough);

}

State(Normal):

while(true) {

Pause(Random(25,50));

GlanceAtRandomPointOfInterest();

yield;

}

State(GlassFullEnough):

Pause(50); // delay before responding

// etc

}

This concept is going against the grain of most programming languages and computer architecture.

It has to be faked.

Page 37: The polling problem

Why Game AI is Difficult - Summary

● No direct language support for multitasking

● Where such support exists, there remains a lack of semantics for switching execution state synchronously (that is, without polling)

● Polling does not scale well

● Complex time dependent interactions mean that without a proper methodology behaviours must be kept simple to reduce complexity

Page 38: The polling problem

Why Game AI is Difficult - Summary

Polling would seem to be at the heart of the problem.

Solutions should focus on this problem.

Page 39: The polling problem

Why Game AI is Difficult - Summary

Typical solutions involve the creation of a virtual machine with a separate domain specific language or encoding of programs in data structures rather than code.

Page 40: The polling problem

Virtual machine solutions

Real Machine (C++)

Virtual Machine

(C#)

Virtual Machine: Behaviour

Trees

Script

Multi-tasking support (yield)

Domain Specific

Language

Page 41: The polling problem

Virtual machine solutions

Real Machine (C++)

Virtual Machine

(C#)

Virtual Machine: Behaviour

Trees

Script

Multi-tasking support (yield)

Domain Specific

LanguageWhy can't we just have a single

language as the solution?

Page 42: The polling problem

Traditional: A light switch

// With polling

function LightSwitch() {

bool lit=false;

while(true) {

if(Switch.state == "Down" && Switch.previousState == "Up") {

lit = !lit;

SetLightState(lit);

}

yield;

}

}

Page 43: The polling problem

Traditional: A light switch with delay

function LightSwitch() {

Timer startTime; bool lit=false;

while(true) {

if(Switch.state == "Down" && Switch.previousState == "Up") {

if(lit) { lit = false;

SetLightState(lit);

} else { lit = true;

startTime = CurrentTime();

SetLightState(lit);

}

}

if(lit && CurrentTime()-startTime > MaxOnTime) {

lit = false;

SetLightState(false);

}

yield;

}

}

Page 44: The polling problem

Messages: A light switch

// Oh nice this is now trivial...

// But only because there's no temporal behaviour

function LightSwitch() {

bool lit=false;

When(ReceivedMessage(eSwitchPressed)) {

lit != lit;

SetLightState(lit);

}

}

Page 45: The polling problem

Messages: A light switch with delayfunction LightSwitch() {

Timer startTime;

bool lit=false;

When(ReceivedMessage(eSwitchPressed)) {

if(lit) { lit = false;

SetLightState(lit);

} else { lit = true;

startTime = CurrentTime();

SetLightState(lit);

}

}

while(true) {

if(lit && CurrentTime()-startTime > MaxOnTime) {

lit = false;

SetLightState(false);

}

yield;

}

}

Page 46: The polling problem

No Polling: A light switch

// pseudocode of language supporting coroutines, messages

// and state transitions

function LightSwitch() {

bool lit=false;

while(true) {

state UnlitState:

when(ReceivedMessage(eSwitchPressed)) { goto state LitState; }

lit=false; SetLightState(lit);

substate(DoNothing());

state LitState:

when(ReceivedMessage(eSwitchPressed)) { goto state UnlitState; }

lit=true; SetLightState(lit);

substate(DoNothing());

}

}

Page 47: The polling problem

No Polling: A light switch with delay

// pseudocode of language supporting coroutines, messages

// and state transitions

function LightSwitch() {

bool lit=false;

while(true) {

state UnlitState:

when(ReceivedMessage(eSwitchPressed)) { goto state LitState; }

lit=false; SetLightState(lit);

substate(DoNothing());

state LitState:

when(ReceivedMessage(eSwitchPressed)) { goto state UnlitState; }

lit=true; SetLightState(lit);

substate(Pause(50)); // That was easy to add!

substate(DoNothing());

}

}

Page 48: The polling problem

PROC system: A light switch with fadefunction FadeTo(float to) {

do { float d = (to - light.currentBrightness)*0.1f;

light.currentBrightness += d;

if(d < 0.001f) {

end;

} } }

function LightSwitch() {

Timer startTime;

bool lit=false;

while(true) {

state UnlitState:

when(ReceivedMessage(eSwitchPressed)) { goto state LitState; }

substate(FadeTo(0));

substate(DoNothing());

state LitState:

when(ReceivedMessage(eSwitchPressed)) { goto state UnlitState; }

substate(FadeTo(1));

substate(DoNothing());

} }

Page 49: The polling problem

The PROC system

● Started as coroutine system in 68000 assembler

● Later implemented in C and C++ where messages were added

● Most recently implemented in C# in Unity 3D

Page 50: The polling problem

The PROC system

● Implements a HFSM with message handling protocols

● Implementation uses nested coroutines

● Has proven very effective at managing complex behaviours

● A solution within the programming language itself

Page 51: The polling problem

PROC system architecture in C# and Unity 3D

IEnumerator method (a PROC)

Control object with a stop flag

looped switch statement

states Message handlers

Straight code

states

states

states

Message handlersMessage handlers

Message handlers

Substate iterators (polling for stop flag)

Page 52: The polling problem

PROC system message handling

Base

Patrol

Goto next patrol point

Goto position

Under Attack

Noise heard

Page 53: The polling problem

PROC system message handling

Base

Patrol

Goto next patrol point

Goto position

Under Attack

Noise heard

Message manager

Noise heard

Page 54: The polling problem

PROC system message handling

Base

Patrol

Goto next patrol point

Goto position

Under Attack

Noise heard

Message manager

Noise heard

Page 55: The polling problem

PROC system message handling

Base

Patrol

Goto next patrol point

Goto position

Under Attack

Noise heard

Message manager

Noise heard

Page 56: The polling problem

PROC system message handling

Base

Patrol

Goto next patrol point

Goto position

Under Attack

Noise heard

Message manager

Noise heard

Terminate

Page 57: The polling problem

PROC system message handling

Base

Patrol

Goto next patrol point

Under Attack

Noise heard

Message manager

Noise heardTerminate

Page 58: The polling problem

PROC system message handling

Base

Patrol

Investigate Noise

Under Attack

Noise heard

Message manager

Noise heard

Page 59: The polling problem

PROC system message handling

Base

Patrol

Investigate Noise

Under Attack

Noise heard

Message manager

Noise heard

Page 60: The polling problem

Implementation in C# / Unity 3D

● Currently uses a pre-processor

● Message handlers are delegates inside methods that can access local variables of the method

● Termination of a task is implemented using a flag which is polled for

● However, this polling is hidden

Page 61: The polling problem

Conclusion

● At the heart of the difficulty of robust and rich behaviour in games is the limitations of popular programming languages

● Many popular solutions create a virtual machine to avoid these problems, often encoding the behaviours in data structures with domain specific languages.

● However, with some small additions to existing languages, this problem could be avoided

● Even without such languages, it is possible to create architectures that solve the problem directly within the language (albeit inelegantly).

Page 62: The polling problem

Conclusion

● The core problem is how to manage the response to input data that changes over time

● This input data is not only from the player, but also between concurrent tasks

● Polling is inescapable, but can be managed

Page 63: The polling problem

Conclusion

● To solve these problems, which are connected not only with game AI, but also with the general problems of scalable multi-tasking programs, you can use this heuristic:

"Design your architecture to hide the

polling problem as much as possible"

Page 64: The polling problem

Thank you

Dino Dini

email: [email protected]

Twitter: dndn1011