Event Loop
Introduction
Python Arcade provides three simple methods to integrate with the event loop.
All three methods are exposed to be overridden in arcade.Window
and arcade.View
. For advanced use cases it is possible to add own
handler via arcade.Window.push_handlers()
.
on_draw()
provides a hook to render to the window. After the on_draw
event, the window
will draw to the screen. By default, this attempts to occur every 1/60 seconds
or once every 16.7 milliseconds. It can be changed when initializing your
arcade.Window
with the draw_rate
argument. Setting the draw rate
to a value above a screen’s refresh rate can cause tearing unless you set the
vsync
argument to true. We recommend keeping your draw_rate
around the
screen’s refresh rate. After every draw event camera state will be reset.
This means that non-default cameras must be reused on every draw event.
on_update()
provides a hook to update state which needs to happen at a roughly regular interval.
The update event is not strictly paired to the draw event, but they share the same
thread. This can cause a bottle-neck if one is significantly slower than the other.
The event also provides a delta_time
argument which is the time elapsed since the
last on_update
event. You can change the rate at which on_update
is called with
the update_rate
argument when initialising your arcade.Window
.
on_fixed_update()
provides a hook to update state which must happen with an exactly regular interval.
Because Arcade can’t ensure the event is actually fired regularly it stores how
much time has passed since the last update, and once enough time has passed it
releases an on_fixed_update
call. The fixed update always provides the same
delta_time
argument. You can change the rate at which on__fixed_update
is
called with the fixed_rate
argument when initialising your arcade.Window
.
Time
While the underlying library, pyglet, provide a clock for scheduling events it is closely tied
to the window’s own events. For simple time keeping Arcade provides global
clock objects. Both clocks can be imported from arcade.clock
as
GLOBAL_CLOCK
and GLOBAL_FIXED_CLOCK
arcade.Clock
The base Arcade clock tracks the elapsed time in seconds, the total number
of clock ticks, and the amount of time that elapsed since the last tick.
The currently active window automatically ticks the GLOBAL_CLOCK
every on_update
.
This means there is no reason to manually tick it. If you need more
clocks, possibly ticking at a different rate, an arcade.Clock
can be created on the fly.
arcade.FixedClock
The fixed clock tracks the same values as the normal clock, but has two special features.
Firstly it enforces that the delta_time
passed into its tick
method is always the same.
This is because advanced physics engines require consistent time. Secondly the fixed clock
requires a sibling regular clock. It uses this clock to track how offset from the true time it is.
Like the regular clock you may make a new arcade.FixedClock
at any time,
but ensure they have a sibling.
Up Coming
In future version of arcade Clock
will be updated to allow for sub clocks.
Sub clocks will be ticked by their parent clock rather than be manually updated. Sub clocks
will make it easier to control the flow of time for specific groups of objects. Such as only
slowing enemies or excluding UI elements. To gain access to a draft arcade.Clock
you can find it in arcade.future.sub_clock
. This version of the sub clock is not final.
If you find any bugs do not hesitate to raise an issue on the github.
More on Fixed update
The on_fixed_update
event can be an extremely powerful tool, but it has many complications
that should be taken into account. If used imporperly the event can grind a game to a halt.
Death Spiral
A fixed update represents a very specific amount of time. If all of the computations take longer than the fixed update represents than the ammount of time accumulated between update events will grow. If this happens for multiple frames the game will begin to spiral. The first few frames of the spiral will lead to one update cycle requiring two fixed update calls. This will increase the extra time accumulated until three fixed updates must occur at once. This will continue to happen until either: the fixed updates start taking less time, or the game crashes.
There are a few solutions to this issue. The simplist method, which works best when there may be spikes in
computation time that quickly settle, is to clamp the max number of fixed updates that can occur in a single
frame. In Arcade this is done by setting the fixed_frame_cap
argument when initialising your
arcade.Window
. The second method is to slow-down time temporarily. By changing the
_tick_speed
of Arcade’s GLOBAL_CLOCK
is is possible to slow down the accumulation of time.
For example setting GLOBAL_CLOCK._tick_speed = 0.5
would allow the fixed update twice as many frames
to calculate for.
Update Interpolation
Because fixed updates work on the accumulation of time this may not sync with
the on_draw
or on_update
events. In extreme cases this can cause a visible stuttering to
objects moved within on_fixed_update
. To prevent this, GLOBAL_FIXED_CLOCK
provides
the accumulated
and fraction``properties. By storing the last frame's position information it is possible
to use ``fraction
to interpolate towards the next calculated positions. For a visual representation of
this effect look at arcade.examples.fixed_update_interpolation
.
Vertical Synchronization
What is vertical sync?
Vertical synchronization (vsync) is a window option in which the video card is prevented from doing anything visible to the display memory until after the monitor finishes its current refresh cycle.
To enable vsync in Arcade:
# On window creation
arcade.Window(800, 600, "Window Title", vsync=True)
# While the application is running
window.set_vsync(True)
This have advantages and disadvantages depending on the situation.
Most windows are what we call “double buffered”. This means the window actually has two surfaces. A visible surface and a hidden surface. All drawing commands will end up in the hidden surface. When we’re done drawing our frame the hidden and visible surfaces swap places and the new frame is revealed to the user.
If this “dance” of swapping surfaces is not timed correctly with your monitor you might experience small hiccups in movement or screen tearing.
Vertical sync disabled as a default
The Arcade window is by default created with vertical sync disabled. This is a much safer default for a number of reasons.
In some environments vertical sync is capped to 30 fps. This can make the game run at half the speed if
delta_time
is not accounted for. We don’t expect beginners takedelta_time
into consideration in their projects.If threads are used all threads will stall while the application is waiting for vertical sync
We cannot guarantee that vertical sync is disabled if this is enforced on driver level. The vast amount of driver defaults lets the application control this.