SlideShare a Scribd company logo
1 of 64
The Polling Problem

Speaker Name
Dino Dini NHTV University of Applied Sciences
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".
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).
Roman Numbers

The Romans had no symbol for 0.

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

Do we lack an appropriate representation in computer
science for doing nothing?
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.
Data In, Data Out

     IMMUTABLE                                COMPUTER                             IMMUTABLE
   INPUT DATA                                 ALGORITHM                           OUTPUT DATA



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.
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?
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.
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...

                                                        Nearly there...

         Wait, sorry I meant 346...



                                             !@@#$@!!
Mutable input data
This is unfortunately common in the real world, and a necessity in effective
game AI.



                  My GPS loves to say "Recalculating..."
The Polling Problem
Q: What do you get when you try to process unpredictable and time
dependent input with a Turing machine?
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;
         }
     }
Say "When"
             While(true) {
                   if(!GlassAcceptablyFull()) {
                         // erm... do nothing???
                         yield();
                   } else {
                         Say("When!");
                         break;
                   }
             }
Say "When"
             While(true) {
                   if(!GlassAcceptablyFull()) {
                         // erm... do nothing???
                         yield();
                   } else {
                         Say("When!");
                         break;
                   }
             }
Say "When"
             While(true) {
                   if(!GlassAcceptablyFull() &&
                         GetGlassStatus() == Normal) {
                         // erm... do nothing???
                         yield();
                   } else {
                         Say("When!");
                         break;
                   }
             }
Say "When"
             // Pourer
             While(true) {
                   if(!Heard("When!")) {
                      KeepPouring();
                      yield();
                   } else {
                      StopPouring();
                   }
             }
Say "When"
             // Pourer
             While(true) {
                   if(!Heard("When!")) {
                      KeepPouring();
                      yield();
                   } else {
                      StopPouring();
                   }
             }
Say "When"
             // Pourer
             While(true) {
                   if(!Heard("When!")) {
                      KeepPouring();
                      yield();
                   } else {
                      StopPouring();
                   }
             }
Say "When"
             // Pourer
             While(true) {
                   if(!Heard("When!") && !GlassFull()) {
                      KeepPouring();
                      yield();
                   } else {
                      StopPouring();
                   }
             }
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
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.
When you have Messages
The typical solution is to use messages, but this does not directly solve 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();
    }
}
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..
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");                Hello, Polling
      while(!stopFlag) { // do nothing
            Yield();
      }
      switch(onMessage) {    // ..etc..
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

}
I wish I could simply say...

function WaitForGlassFill() {

    SetAnimationState("ObserveFilling");

    When(ReceivedMessage(eOnGlassAcceptablyFull)) {
          SayWhen();
          StartNormalAnimationState();              Here I have a trivial case,
                                                    but maybe I want some
          End;   // this will stop this coroutine
                                                    other tasks going on here
    }                                               too, like watching out for
                                                    a sniper.
    DoNothing(); // until coroutine terminated

}
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;

}
I want reusable behaviours
function WaitForGlassFill() {
      SetAnimationState("ObserveFilling");

      When(ReceivedMessage(eOnGlassAcceptablyFull)) {
            SayWhen();
            StartNormalAnimationState();
            end;
      }

       while(true) {
             Pause(Random(25,50));
             GlanceAtRandomPointOfInterest();
             yield;
      }
  }
I want reusable behaviours
function WaitForGlassFill() {
      SetAnimationState("ObserveFilling");

       When(ReceivedMessage(eOnGlassAcceptablyFull)) {
             SayWhen();
             StartNormalAnimationState();
             end;
       }                                               This needs to be
                                                       interuptable when
                                                       WaitForGlassFill handles a
       while(true) {                                   message and switches
             Pause(Random(25,50));                     task.
             GlanceAtRandomPointOfInterest();
             yield;
      }
  }
I want reusable behaviours
function WaitForGlassFill() {
      SetAnimationState("ObserveFilling");

      When(ReceivedMessage(eOnGlassAcceptablyFull)) {
            SayWhen();
            StartNormalAnimationState();
            end;
      }
                                                        And what if we want to do
                                                        something that is not
       while(true) {                                    instantaneous?
             Pause(Random(25,50));
             GlanceAtRandomPointOfInterest();
             yield;
      }
  }
I want reusable behaviours
function WaitForGlassFill() {
      SetAnimationState("ObserveFilling");

      When(ReceivedMessage(eOnGlassAcceptablyFull)) {
            SayWhen();
            Pause(50);
            StartNormalAnimationState();
            end;
      }

       while(true) {
             Pause(Random(25,50));
             GlanceAtRandomPointOfInterest();
             yield;
      }
  }
I want reusable behaviours
function WaitForGlassFill() {
      SetAnimationState("ObserveFilling");

       When(ReceivedMessage(eOnGlassAcceptablyFull)) {
             SayWhen();
             Pause(50);
             StartNormalAnimationState();
             end;
                                                       No! Can't do this. It would
       }
                                                       cause the calling object to
                                                       Pause (at best).
       while(true) {
             Pause(Random(25,50));
             GlanceAtRandomPointOfInterest();
             yield;
      }
  }
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
  }
Messages must change the "program counter"
function WaitForGlassFill() {
      SetAnimationState("ObserveFilling");
      When(ReceivedMessage(eOnGlassAcceptablyFull)) {
            SwitchTo(GlassFullEnough);                  This concept is going
      }                                                 against the grain of most
      State(Normal):                                    programming languages
                                                        and computer
            while(true) {
                                                        architecture.
                  Pause(Random(25,50));
                  GlanceAtRandomPointOfInterest();      It has to be faked.
                  yield;
            }
      State(GlassFullEnough):
            Pause(50);    // delay before responding
            // etc
  }
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
Why Game AI is Difficult - Summary
Polling would seem to be at the heart of the problem.

Solutions should focus on this 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.
Virtual machine solutions


                                       Script

               Virtual      Virtual
   Real                                      Domain
               Machine     Machine:
 Machine                                     Specific
                (C#)       Behaviour
  (C++)                                     Language
                             Trees

           Multi-tasking
             support
              (yield)
Virtual machine solutions


                                             Script

               Virtual      Virtual
   Real                                              Domain
               Machine     Machine:
 Machine                                             Specific
                (C#)       Behaviour
  (C++)                                             Language
                                     Why can't we just
                             Trees
                                        have a single
                                      language as the
           Multi-tasking                  solution?
             support
              (yield)
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;
      }
}
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;
      }
}
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);
      }
}
Messages: A light switch with delay
function 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;
      }
}
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());
      }
}
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());
      }
}
PROC system: A light switch with fade
function 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());
} }
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
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
PROC system architecture in C# and Unity 3D
     IEnumerator method (a PROC)


               Control object with a stop flag


                 looped switch statement


      states                               Message handlers
                                            Message handlers
         states                               Message handlers
           states                               Message handlers
              states


                        Straight code


                            Substate iterators (polling for stop flag)
PROC system message handling
                                      Under Attack
       Base



                                               Noise heard
              Patrol



                       Goto next patrol point



                               Goto position
PROC system message handling
                                      Under Attack
       Base



                                               Noise heard
              Patrol


                                                             Message manager
                       Goto next patrol point
                                                               Noise heard


                               Goto position
PROC system message handling
                                      Under Attack
       Base



                                               Noise heard
              Patrol


                                                             Message manager
                       Goto next patrol point
                                                               Noise heard


                               Goto position
PROC system message handling
                                      Under Attack
       Base



                                               Noise heard
              Patrol


                                                             Message manager
                       Goto next patrol point
                                                               Noise heard


                               Goto position
PROC system message handling
                                      Under Attack
       Base



                                               Noise heard
              Patrol


                                                             Message manager
                       Goto next patrol point
                                                               Noise heard

              Terminate
                               Goto position
PROC system message handling
                                      Under Attack
       Base



                                            Noise heard
              Patrol


                                                          Message manager
                       Goto next patrol point
      Terminate                                             Noise heard
PROC system message handling
                          Under Attack
          Base



                               Noise heard
                 Patrol


                                             Message manager

    Investigate Noise
                                               Noise heard
PROC system message handling
                           Under Attack
          Base



                                Noise heard
                  Patrol


                                              Message manager

    Investigate Noise
                                                Noise heard



              …
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
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).
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
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"
Thank you
        Dino Dini




        email: dndn1011@gmail.com
        Twitter: dndn1011

More Related Content

Similar to The polling problem

Introduction to Kotlin.pptx
Introduction to Kotlin.pptxIntroduction to Kotlin.pptx
Introduction to Kotlin.pptxAzharFauzan9
 
01 Introduction to Kotlin - Programming in Kotlin.pptx
01 Introduction to Kotlin - Programming in Kotlin.pptx01 Introduction to Kotlin - Programming in Kotlin.pptx
01 Introduction to Kotlin - Programming in Kotlin.pptxIvanZawPhyo
 
05 JavaScript #burningkeyboards
05 JavaScript #burningkeyboards05 JavaScript #burningkeyboards
05 JavaScript #burningkeyboardsDenis Ristic
 
JavaScript - Like a Box of Chocolates
JavaScript - Like a Box of ChocolatesJavaScript - Like a Box of Chocolates
JavaScript - Like a Box of ChocolatesRobert Nyman
 
JavaScript & HTML5 - Brave New World
JavaScript & HTML5 - Brave New WorldJavaScript & HTML5 - Brave New World
JavaScript & HTML5 - Brave New WorldRobert Nyman
 
Web Optimization Summit: Coding for Performance
Web Optimization Summit: Coding for PerformanceWeb Optimization Summit: Coding for Performance
Web Optimization Summit: Coding for Performancejohndaviddalton
 
Javascript best practices
Javascript best practicesJavascript best practices
Javascript best practicesManav Gupta
 
How to Start Test-Driven Development in Legacy Code
How to Start Test-Driven Development in Legacy CodeHow to Start Test-Driven Development in Legacy Code
How to Start Test-Driven Development in Legacy CodeDaniel Wellman
 
Unbearable Test Code Smell
Unbearable Test Code SmellUnbearable Test Code Smell
Unbearable Test Code SmellSteven Mak
 

Similar to The polling problem (11)

Pj01 5-exceution control flow
Pj01 5-exceution control flowPj01 5-exceution control flow
Pj01 5-exceution control flow
 
Introduction to Kotlin.pptx
Introduction to Kotlin.pptxIntroduction to Kotlin.pptx
Introduction to Kotlin.pptx
 
01 Introduction to Kotlin - Programming in Kotlin.pptx
01 Introduction to Kotlin - Programming in Kotlin.pptx01 Introduction to Kotlin - Programming in Kotlin.pptx
01 Introduction to Kotlin - Programming in Kotlin.pptx
 
05 JavaScript #burningkeyboards
05 JavaScript #burningkeyboards05 JavaScript #burningkeyboards
05 JavaScript #burningkeyboards
 
JavaScript - Like a Box of Chocolates
JavaScript - Like a Box of ChocolatesJavaScript - Like a Box of Chocolates
JavaScript - Like a Box of Chocolates
 
JavaScript & HTML5 - Brave New World
JavaScript & HTML5 - Brave New WorldJavaScript & HTML5 - Brave New World
JavaScript & HTML5 - Brave New World
 
What JS? Itself
What JS? ItselfWhat JS? Itself
What JS? Itself
 
Web Optimization Summit: Coding for Performance
Web Optimization Summit: Coding for PerformanceWeb Optimization Summit: Coding for Performance
Web Optimization Summit: Coding for Performance
 
Javascript best practices
Javascript best practicesJavascript best practices
Javascript best practices
 
How to Start Test-Driven Development in Legacy Code
How to Start Test-Driven Development in Legacy CodeHow to Start Test-Driven Development in Legacy Code
How to Start Test-Driven Development in Legacy Code
 
Unbearable Test Code Smell
Unbearable Test Code SmellUnbearable Test Code Smell
Unbearable Test Code Smell
 

The polling problem

  • 1. The Polling Problem Speaker Name Dino Dini NHTV University of Applied Sciences
  • 2. 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".
  • 3. 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).
  • 4. Roman Numbers The Romans had no symbol for 0. The result was that it held back their development of technology and science.
  • 5. Doing Nothing Do we lack an appropriate representation in computer science for doing nothing?
  • 6. 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.
  • 7. Data In, Data Out IMMUTABLE COMPUTER IMMUTABLE INPUT DATA ALGORITHM OUTPUT DATA 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.
  • 8. 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?
  • 9. 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.
  • 10. 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... Nearly there... Wait, sorry I meant 346... !@@#$@!!
  • 11. Mutable input data This is unfortunately common in the real world, and a necessity in effective game AI. My GPS loves to say "Recalculating..."
  • 12. The Polling Problem Q: What do you get when you try to process unpredictable and time dependent input with a Turing machine?
  • 13. 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; } }
  • 14. Say "When" While(true) { if(!GlassAcceptablyFull()) { // erm... do nothing??? yield(); } else { Say("When!"); break; } }
  • 15. Say "When" While(true) { if(!GlassAcceptablyFull()) { // erm... do nothing??? yield(); } else { Say("When!"); break; } }
  • 16. Say "When" While(true) { if(!GlassAcceptablyFull() && GetGlassStatus() == Normal) { // erm... do nothing??? yield(); } else { Say("When!"); break; } }
  • 17. Say "When" // Pourer While(true) { if(!Heard("When!")) { KeepPouring(); yield(); } else { StopPouring(); } }
  • 18. Say "When" // Pourer While(true) { if(!Heard("When!")) { KeepPouring(); yield(); } else { StopPouring(); } }
  • 19. Say "When" // Pourer While(true) { if(!Heard("When!")) { KeepPouring(); yield(); } else { StopPouring(); } }
  • 20. Say "When" // Pourer While(true) { if(!Heard("When!") && !GlassFull()) { KeepPouring(); yield(); } else { StopPouring(); } }
  • 21. 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
  • 22. 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.
  • 23. When you have Messages The typical solution is to use messages, but this does not directly solve the polling problem.
  • 24. "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(); } }
  • 25. 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..
  • 26. 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"); Hello, Polling while(!stopFlag) { // do nothing Yield(); } switch(onMessage) { // ..etc..
  • 27. 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 }
  • 28. I wish I could simply say... function WaitForGlassFill() { SetAnimationState("ObserveFilling"); When(ReceivedMessage(eOnGlassAcceptablyFull)) { SayWhen(); StartNormalAnimationState(); Here I have a trivial case, but maybe I want some End; // this will stop this coroutine other tasks going on here } too, like watching out for a sniper. DoNothing(); // until coroutine terminated }
  • 29. 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; }
  • 30. I want reusable behaviours function WaitForGlassFill() { SetAnimationState("ObserveFilling"); When(ReceivedMessage(eOnGlassAcceptablyFull)) { SayWhen(); StartNormalAnimationState(); end; } while(true) { Pause(Random(25,50)); GlanceAtRandomPointOfInterest(); yield; } }
  • 31. I want reusable behaviours function WaitForGlassFill() { SetAnimationState("ObserveFilling"); When(ReceivedMessage(eOnGlassAcceptablyFull)) { SayWhen(); StartNormalAnimationState(); end; } This needs to be interuptable when WaitForGlassFill handles a while(true) { message and switches Pause(Random(25,50)); task. GlanceAtRandomPointOfInterest(); yield; } }
  • 32. I want reusable behaviours function WaitForGlassFill() { SetAnimationState("ObserveFilling"); When(ReceivedMessage(eOnGlassAcceptablyFull)) { SayWhen(); StartNormalAnimationState(); end; } And what if we want to do something that is not while(true) { instantaneous? Pause(Random(25,50)); GlanceAtRandomPointOfInterest(); yield; } }
  • 33. I want reusable behaviours function WaitForGlassFill() { SetAnimationState("ObserveFilling"); When(ReceivedMessage(eOnGlassAcceptablyFull)) { SayWhen(); Pause(50); StartNormalAnimationState(); end; } while(true) { Pause(Random(25,50)); GlanceAtRandomPointOfInterest(); yield; } }
  • 34. I want reusable behaviours function WaitForGlassFill() { SetAnimationState("ObserveFilling"); When(ReceivedMessage(eOnGlassAcceptablyFull)) { SayWhen(); Pause(50); StartNormalAnimationState(); end; No! Can't do this. It would } cause the calling object to Pause (at best). while(true) { Pause(Random(25,50)); GlanceAtRandomPointOfInterest(); yield; } }
  • 35. 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 }
  • 36. Messages must change the "program counter" function WaitForGlassFill() { SetAnimationState("ObserveFilling"); When(ReceivedMessage(eOnGlassAcceptablyFull)) { SwitchTo(GlassFullEnough); This concept is going } against the grain of most State(Normal): programming languages and computer while(true) { architecture. Pause(Random(25,50)); GlanceAtRandomPointOfInterest(); It has to be faked. yield; } State(GlassFullEnough): Pause(50); // delay before responding // etc }
  • 37. 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
  • 38. Why Game AI is Difficult - Summary Polling would seem to be at the heart of the problem. Solutions should focus on this problem.
  • 39. 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.
  • 40. Virtual machine solutions Script Virtual Virtual Real Domain Machine Machine: Machine Specific (C#) Behaviour (C++) Language Trees Multi-tasking support (yield)
  • 41. Virtual machine solutions Script Virtual Virtual Real Domain Machine Machine: Machine Specific (C#) Behaviour (C++) Language Why can't we just Trees have a single language as the Multi-tasking solution? support (yield)
  • 42. 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; } }
  • 43. 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; } }
  • 44. 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); } }
  • 45. Messages: A light switch with delay function 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; } }
  • 46. 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()); } }
  • 47. 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()); } }
  • 48. PROC system: A light switch with fade function 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()); } }
  • 49. 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
  • 50. 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
  • 51. PROC system architecture in C# and Unity 3D IEnumerator method (a PROC) Control object with a stop flag looped switch statement states Message handlers Message handlers states Message handlers states Message handlers states Straight code Substate iterators (polling for stop flag)
  • 52. PROC system message handling Under Attack Base Noise heard Patrol Goto next patrol point Goto position
  • 53. PROC system message handling Under Attack Base Noise heard Patrol Message manager Goto next patrol point Noise heard Goto position
  • 54. PROC system message handling Under Attack Base Noise heard Patrol Message manager Goto next patrol point Noise heard Goto position
  • 55. PROC system message handling Under Attack Base Noise heard Patrol Message manager Goto next patrol point Noise heard Goto position
  • 56. PROC system message handling Under Attack Base Noise heard Patrol Message manager Goto next patrol point Noise heard Terminate Goto position
  • 57. PROC system message handling Under Attack Base Noise heard Patrol Message manager Goto next patrol point Terminate Noise heard
  • 58. PROC system message handling Under Attack Base Noise heard Patrol Message manager Investigate Noise Noise heard
  • 59. PROC system message handling Under Attack Base Noise heard Patrol Message manager Investigate Noise Noise heard …
  • 60. 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
  • 61. 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).
  • 62. 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
  • 63. 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"
  • 64. Thank you Dino Dini email: dndn1011@gmail.com Twitter: dndn1011