Devurandoms Lua Proposal

From WarzoneWiki

Jump to: navigation, search

Contents

Events

Prerequisites

You should know how tables work in Lua and what userdata is.

Knowing how SDL timers work is also helpful.

Data structures

Only one needed: A big table, I'll call it event_table.

Timer-like events index with number ("timestamp") into it, like this: event_table[12]. These will be the events that need to be executed at the 12th game-tick (absolute value).

Callback-style events use an Event-userdata as index, I'll write it like this: event_table[CALL_DROIDKILLED].

Under a given index in table, there is another, unordered table: event_table[idx] = {}. That is so you can assign more than one eventhandler to an event. For simplicities sake I will ignore this fact below!

Internal functions

A ticker function, which will do 2 things:

1. Iterate through the indexes from lastTick to currentTick and execute the eventhandler associated with the time i, if any.

 for (i = lastTick ; i < currentTick ; i++ )
   event_table[i]()

2. Walk through the event stack (since the last frame), executing any associated eventhandler, if any:

 while( event = event_stack.pop() )
   event_table[event]()


Alternatively to (2), you can of course immediately execute an event at the time it happens. For example where delaying is not possible, eg. for "died" events, where the droid might already be deleted when we come to the eventhandling.
Note: Currently (svn/trunk) droid deletion is delayed for one frame to prevent such situations.


Actually, step (1) has to look a bit more complicated, so that the return-value of the function is used to reschedule it (relative time):

 for (i = lastTick ; i < currentTick ; i++ )
   event_table[ now + event_table[i]() ] = event_table[i]

External functions

 event.link(event, handler)

Links the handler to be executed when the specified event happens. (Remember: If event is a number, it specifies a relative time, counted in ticks from now)


Implemented for example like this:

 function link(event, handler)
   if isnumber event then
     event_table[now + event] = handler
   else
     event_table[event] = handler
   end
 end


Alternative proposal

 id = event.link(event, handler) -- parameters like above
 function link(event, handler)
   id = find_free_id() -- Get a free id
 
   if isnumber event then
     event += now -- Times are relative to 'now'
   end
 
   event_table[id] = { event, handler } -- Store it using the id as an index
 
   return id -- return the id, so it eventhandler can be unlinked
 end


The associated internal function would have to look like this (only necessary for time-based events, since the others are called immediately):

 for id, blob in pairs(event_table) do
   if isnumber blob[1] and blob[1] <= now then -- check whether 'event' is a time and lies in the past
     nextTime = blob[2]()
     if nextTime != nil then -- Reschedule if we got a new time
       event_table[id] = { now + nextTime, handler, condition }
     else
       event_table[id] = nil -- Don't schedule it again if it returned nil
     end
   end
 end


Use the returned id to unlink events again:

 event.unlink(id)
 function unlink(id)
   make_free_id(id) -- Put the id back into the pool
 
   event_table[id] = nil -- Remove it from the table
 end


Example:

 -- Destroy nexus after 1000 ticks:
 id = event.link( 10, function() if now > 1000 then nexus.destroy() end end )
 event.unlink(id)

Examples

Assuming now contains the current time/tick.

 event.link( 10, function() print "hello" end ) -- Will print "hello" 10 ticks from now, just once.
 event.link( 10, function()
                   print "hello again"
                   return now + 10
                 end ) -- Will print "hello again" 10 ticks from now and continue doing that every 10 ticks
 event.link( event.callback.droidkilled, function() print "ouch" end ) -- Will print "ouch" whenever a droid gets killed
 event.link( event.callback.droidkilled, function(player)
                                           if player == game.player.green then
                                             print "dont hurt me"
                                           end
                                         end ) -- Will print "dont hurt me" whenever a droid of player "green" gets killed
 event.link( event.init, function() print "hello world" end ) -- Print hello world at the beginning of the game.

Convenience functions, WZS style

handler is always a function!


 every(time, handler)

A wrapper, which returns 2 values which shall be passed to event.link(). 1st value is the time of first execution, 2nd value is a function to repeatedly schedule the execution

 function every(time, handler) -- every 'time' ticks call 'handler'
   return time, function() -- return 2 values, initial execution time + eventhandler
                  handler() -- execute the eventhandler
                  return time -- fire again in 'time' ticks
                end
 end


 wait(time, handler)

A very simple wrapper to schedule handler to execute in time ticks:

 function wait(time, handler)
   return time, handler


Convenience functions, Gerard-style

handler is always a function!


 repeatedEvent(time, handler)

Will register handler to execute every time ticks. Could look like this:

 function repeatedEvent(time, handler)
   event.link( every( time, handler ) )


 booleanEvent(condition, handler)

Run handler, when condition evaluates to not nil. condition is a function! Could look like this:

 function booleanEvent(condition, handler) 
   event.link( every( 1, function()
                           if condition() then
                             handler()
                           end
                          end )