DragonRuby: Static Outputs

In a previous post we looked at different ways to render outputs (sprites, rectangles, lines, etc.) in the DragonRuby Game Toolkit.

The post ended by hinting at a more efficient way to render outputs instead of adding them to e.g. args.outputs.solids or args.outputs.sprites each tick.

This post explores the world of “static outputs”!

Static What?

First of all, we should address the most confusing part of all this.

“Static” does not mean the images don’t move or change. Instead, it means that render “queue” is not cleared after every tick.

Normally, one would load up the queue each tick, like this:

def tick args

  # Render a black rectangle
  args.outputs.solids << {
    x: 100,
    y: 200,
    w: 300,  # width
    h: 400   # height
  }
end

But this is kind of wasteful. We are creating a new hash table each tick (60 ticks/second) and then throwing it away. Also each tick we are filling up the args.outputs.solids queue and then emptying it.

Instead, why not create the hash table once, load up the queue once, and then re-use them?

That’s the idea of static outputs!

There are static versions for each rendered type:

  • args.outputs.static_borders
  • args.outputs.static_labels
  • args.outputs.static_primitives
  • args.outputs.static_solids
  • args.outputs.static_sprites

Going Static

Starting Out

Here’s an example with comments explaining what the code is doing. This “game” simply moves a square back and forth across the screen. This is the entire program!

def tick args
  # Initialize the x location of the square
  args.state.x ||= 0

  # Initialize the direction/velocity
  args.state.direction ||= 10

  # If we hit the sides, change direction
  if args.state.x > args.grid.right or args.state.x < args.grid.left
    args.state.direction = -args.state.direction
  end

  # Update the x location
  args.state.x += args.state.direction

  # Build the square
  square = {
    x: args.state.x,
    y: 400,
    w: 40,
    h: 40,
  }

  # Add the square to the render queue
  args.outputs.solids << square
end

The resulting output looks like:

Image description

This example introduces args.state. This is basically a persistent bag you can throw anything into. (For Rubyists - this is like OpenStruct.)

x and direction are not special, they are just variables we are defining. We use ||= to initialize them because we only want to set the values on the first tick.

This example illustrates the point from above - every tick it creates a new square and adds it to the queue. The queue is emptied out and then the code starts all over again.

Seems wasteful, right?

Caching the Objects

First thing I think of is - “why not create the square once, then just update the object each tick? Does that work?” Yes! It does.

def tick args
  args.state.direction ||= 10
  args.state.square ||= {
    x: 0,
    y: 400,
    w: 40,
    h: 40,
  }

  if args.state.square[:x] > args.grid.right or args.state.square[:x] < args.grid.left
    args.state.direction = -args.state.direction
  end

  args.state.square[:x] += args.state.direction

  args.outputs.solids << args.state.square
end

In this code, we create the square only once and then store it in args.state.square.

Instead of having a separate x variable, the code updates the x property on the square directly.

This is better, but we are still updating args.outputs.solids each tick.

Full Static

def tick args
  args.state.direction ||= 10
  args.state.square ||= {
    x: 0,
    y: 400,
    w: 40,
    h: 40,
  }

  # On the first tick, add the square to the render queue
  if args.tick_count == 0
    args.outputs.static_solids << args.state.square
  end

  if args.state.square[:x] > args.grid.right or args.state.square[:x] < args.grid.left
    args.state.direction = -args.state.direction
  end

  args.state.square[:x] += args.state.direction
end

In this code, we use the fact that the first args.tick_count is 0 to add the square to args.outputs.static_solids just once. It will continue to be rendered on each tick.

Performance

Intuitively, since the code is doing less, it should be faster. But does it really make a difference?

It depends on your game, how much it’s doing per tick, how many sprites you are rendering, and what platform/hardware it’s running on.

The examples above? Not going to see any difference using static_solids.

But DragonRuby contains two examples that directly compare args.outputs.sprites vs. args.outputs.static_sprites (here and here).

In these examples, you can play with the number of “stars” rendered to see different performance. On my ancient laptop, I do not see a performance difference until around 3,000 stars.

Your mileage may vary, though!

Should I Always Use the Static Versions?

It depends! Probably not?

If your code mainly manipulates the same objects around the screen and always renders them in the same order, then using the static_ approach might be simpler and faster.

But in many cases it might be easier to simply set up the render queues each tick, especially if the objects rendered or their ordering change regularly. Otherwise, managing the state of the rendering queues can become cumbersome. (We haven’t even talked about clearing the static queues, for example.)

Some of this comes down to personal preference and how you would like to structure your code. But hopefully this post has helped explain how to use the args.outputs.static_* methods in your game!

API Levels in DragonRuby Game Toolkit

DragonRuby Game Toolkit (DRGTK) is a 2D game engine built with mRuby, SDL, and LLVM. It’s meant to be tiny, fast, and allow you to turn out games quickly using Ruby.

Unfortunately, since the documentation is focused on making games quickly, I sometimes get lost when trying to figure out how to do things that should be simple. DragonRuby seems to have borrowed Perl’s “There’s more than one way to do it” philosophy because for anything you want to do with the API there are several ways to do it.

The documentation and examples tend to focus on the simplest forms (which is fine) but then require digging and experimentation to figure out the rest.

To help explain/document the different API options, this post will go through different methods of rendering images (well, rectangles mostly).

API Levels

Level 0 - Getting Started

The main object one interacts with in DragonRuby is canonically called args (always accessible with $gtk.args… because there’s more than one way!)

To output things, like shapes, sprites, or sounds, you can use the “shovel” operator << on args.outputs - like args.outputs.sprites or args.outputs.sounds.

For these “basic” objects, you’ll need to shovel things in on every “tick” of the game engine.

This is a complete DragonRuby example to output a rectangle:

def tick args
  args.outputs.solids << [100, 200, 300, 400]
end

So far, so good.

(You can assume the rest of the examples below are inside a tick method if it’s not explicitly defined.)

Level 1 - Arrays

The documentation usually starts off by passing things to args.outputs as arrays - essentially positional arguments.

For example:

args.outputs.solids << [100, 200, 300, 400]

What does that do? I’m not quite sure!

A black rectangle on a gray background

Okay - it shows a black rectangle on the screen. Not that exciting, but useful enough for our examples.

The problem with using arrays though is remembering which index in the array is which attribute. On top of that, it’s not even recommended to pass in arrays because they are slow (for some reason).

Level 2 - Hashes

What is better than arrays? Hashes! (Hash tables/associative arrays for anyone not familiar with Ruby.)

args.outputs.solids << {
  x: 100,
  y: 200,
  w: 300,  # width
  h: 400   # height
}

Okay, that’s way easier to understand!

And there are more options, too:

args.outputs.solids << {
  x: 100,
  y: 200,
  w: 300,
  h: 400,
  r: 255,  # red
  g: 200,  # green
  b: 255,  # blue
  a: 100,  # alpha
  blendmode_enum: 0  # blend mode
}

Level 3 - Primitives

But there is yet another way… instead of using args.outputs.solids, args.outputs.labels, args.outputs.sprites, etc., we can output a hash to args.outputs.primitives but mark it as the right primitive “type”:

args.outputs.primitives << {
  x: 100,
  y: 200,
  w: 300,
  h: 400,
  r: 255,  # red
  g: 200,  # green
  b: 255,  # blue
  a: 100,  # alpha
  blendmode_enum: 0  # blend mode
}.solid!

Weird, but okay. Why might one want to do this? See the “Layers” section down below!

Level 4 - Classes

Finally, probably the most natural for a Rubyist: just use a class!

To do this, you must define all the methods expected for the type of primitive, plus define a method called primitive_marker that returns the type of primitive.

class ACoolSolid
  attr_reader :x, :y, :w, :h, :r, :g, :b, :a, :blendmode_enum

  def initialize x, y, w, h
    @x = x
    @y = y
    @w = w
    @h = h
  end

  def primitive_marker
    :solid
  end
end

def tick args
  args.outputs.primitives << ACoolSolid.new(100, 200, 300, 400)
end

Instead of defining a bunch of methods with attr_reader, you can use attr_sprite instead which is a DragonRuby shortcut method to do the same thing.

Layers

DragonRuby renders outputs in this order (from back to front):

  • Solids
  • Sprites
  • Primitives
  • Labels
  • Lines
  • Borders

For each “layer” the objects are rendered in FIFO order - the first things in the queue are rendered first.

But wait… one of these things is not like the others. Doesn’t primitives just hold things like solids, sprites, labels…?

Yes!

But using primitives enables better control over render order.

For example, what if we want to render a rectangle on top of a sprite? With the fixed rendering order above, it’s impossible! But by using args.outputs.primitives we can do it:

def tick args
  a_solid = {
    x: 100,
    y: 200,
    w: 300,
    h: 400
  }.solid!

  a_sprite = {
    x: 100,
    y: 200,
    w: 500,
    h: 500,
    path: 'metadata/icon.png'
  }.sprite!

  args.outputs.primitives << a_sprite << a_solid
end

And here’s the proof:

Screenshot showing a black rectangle on top of the DragonRuby logo

Every Tick?

args.outputs.sprites, etc. get cleared after each call to tick. So every tick we have to recreate all the objects and pass them in to args.outputs. Seems wasteful, right? Yes, it is!

It’s somewhat odd that most DragonRuby examples show creating arrays or hashes for primitives each tick. It made me think somehow the rendering process was destructive - were the things added into args.outputs destroyed or modified in some way?

Turns out, no. It is fine to create e.g. a sprite representation once and render the same object each time.

Here we’ll use a global for demonstration purposes:

class ACoolSolid
  attr_reader :x, :y, :w, :h, :r, :g, :b, :a, :blendmode_enum

  def initialize x, y, w, h
    @x = x
    @y = y
    @w = w
    @h = h
  end

  def primitive_marker
    :solid
  end
end

$a_solid = ACoolSolid.new(10, 20, 30, 40)

def tick args
  args.outputs.primitives << $a_solid
end

But Wait…

We are still shoveling an object into args.outputs.primitives each time. Surely that is unnecessary?

Correct! There are static versions for each args.outputs (e.g. args.outputs.static_solids) that do not get cleared every tick.

Naturally, this is more efficient than creating objects and updating the outputs 60 times per second.

We’ll explore these options in a future post, but be aware they are available!

Fixing Just One False Positive in Brakeman

A while ago, I came across a Brakeman false positive that I wanted to fix.

For just one false positive, it became a bit of an epic journey.

The code looked something like this:

class Task < ApplicationRecord
  enum status: {
    pending: 0,
    success: 1,
    failed: 3,
  }
  NOT_FAILURES = ['pending', 'success'].freeze
end

class TaskRunner
  def get_failures
    start_time = Date.beginning_of_quarter
    end_time = Date.end_of_quarter

    no_failure_enums = Task.statuses.values_at(*Task::NOT_FAILURES)

    query = <<~QUERY
      SELECT COUNT(*)
      FROM `tasks`
      WHERE `tasks`.`status` NOT IN (#{no_failure_enums.join(',')})
      AND `tasks`.`time_end` BETWEEN #{start_time} AND #{end_time}
    QUERY

    # Line below triggers a SQL injection warning
    Task.connection.select_all(query)
  end
end

(You can imagine in reality the query is a bit more complicated and justifies writing it this way.)

This code results in an SQL injection warning from Brakeman:

Confidence: High
Category: SQL Injection
Check: SQL
Message: Possible SQL injection
Code: Task.connection.select_all("SELECT COUNT(*)\nFROM `filings`\nWHERE `filings`.`status` NOT IN (#{Task.statuses.values_at(*["pending", "success"]).join(",")})\nAND `filings`.`time_end` BETWEEN #{Date.beginning_of_quarter} AND #{Date.end_of_quarter}\n")
File: app/models/task.rb
Line: 25

This is a little hard to read, so let’s take a look at a better formatted version of the code Brakeman is complaining about:

Task.connection.select_all(
  "SELECT COUNT(*) \
  FROM `filings` \
  WHERE `filings`.`status` NOT IN (#{Task.statuses.values_at(*["pending", "success"]).join(",")}) \
  AND `filings`.`time_end` BETWEEN #{Date.beginning_of_quarter} \
  AND #{Date.end_of_quarter}"
)

Brakeman is warning about this SQL query because it is using string interpolation to unsafely add in values to the query. If an attacker could control those values, they could modify the SQL run by the database.

In particular, it’s warning about

Task.statuses.values_at(*["pending", "success"]).join(",")

(the value in no_failure_enums.join(',')).

However, in this case, we know that Task.statuses is actually a constant - it’s defined using enum in the Task class. This code is just grabbing the integer values for the given enums and joining them back into a comma-separated string.

So how do we get Brakeman to understand that this value is actually safe?

Splatted Arrays

Let’s dive in!

The call we care about:

Task.statuses.values_at(*["pending", "success"]).join(",")

First up is *["pending", "success"]. This code converts an array of strings to individual method arguments (i.e., values_at("pending", "success").

This is pretty easy to handle. In the case where a splatted array is the only argument to a method, just use the elements of the array as the argument list. (Check out the pull request here)

This gets us to:

Task.statuses.values_at("pending", "success").join(",")

Better!

Hash Values

In this case, values_at is Hash#values_at - it returns an array of values from the hash table for the given keys.

This is also not too difficult to implement. I went ahead and covered Hash#values at the same time. (Check out the pull request here)

Brakeman will now do something like this:

h = { a: 1, b: 2, c: 3 }
h.values_at(:a, :c)  #=> [1, 3]

Great! Back to our code sample, how does it look now?

Task.statuses.values_at("pending", "success").join(",")

Oh… it looks exactly the same because Brakeman has no idea what Task.statuses is.

Okay, no problem. We just need to implement support for ActiveRecord’s enum.

Detour!

Here I took a little detour. Task.statuses is a method that returns a hash value. Instead of just implementing that, I thought this would be a good time to support methods with single, simple return values.

For example:

class Dog
  def self.sound
    'bark'
  end
end

If Brakeman could know that Dog.sound returns 'bark', I could implement enums as method definitions that return simple arrays or hashes. (More on this later!)

To implement this functionality, I rewrote how Brakeman tracks methods (as real objects) and updated some method lookup code.

The details aren’t particularly interesting, but the code changes are here.

Back to Enums

Calling enum essentially defines a bunch of methods.

For example this code:

class Task < ApplicationRecord
  enum status: {
    pending: 0,
    success: 1,
    failed: 3,
  }
end

Will define methods like

Task.statuses # the one we care about!
Task.status
Task#status
Task#pending?
Task#success?
# ..etc.

You can pass in an explicit hash mapping keys to values or just an array of keys and Rails will do the mapping.

To implement this in Brakeman, we simulate the creation of the status and statuses methods:

class Task < ApplicationRecord
  def self.statuses
    {
      pending: 0,
      success: 1,
      failed: 3,
    }
  end
end

Then rely on the previous changes for Task.statuses now returning a hash.

Another Detour

You might have noticed that the enum definition uses status but we need statuses.

This required a tiny tweak to Brakeman’s extremely over-simplified pluralize.

Back to the False Positive

Where are we now?

With enum support and proper pluralization, this code:

Task.statuses.values_at(*["pending", "success"]).join(",")

Gets reduced like this:

{
  pending: 0,
  success: 1,
  failed: 3,
}.values_at(*["pending", "success"]).join(",")

to

{
  pending: 0,
  success: 1,
  failed: 3,
}.values_at("pending", "success").join(",")

to

[:BRAKEMAN_SAFE_LITERAL, :BRAKEMAN_SAFE_LITERAL].join(",")

to

"BRAKEMAN_SAFE_LITERAL,BRAKEMAN_SAFE_LITERAL"

Well… it’s not perfect. Note that the array values are strings, but our enum uses symbol keys. But Brakeman knows the enum is all literal values which are safe, so we end up with this.

The query now looks like:

Task.connection.select_all(
  "SELECT COUNT(*) \
  FROM `filings` \
  WHERE `filings`.`status` NOT IN (#{"BRAKEMAN_SAFE_LITERAL,BRAKEMAN_SAFE_LITERAL"}) \
  AND `filings`.`time_end` BETWEEN #{Date.beginning_of_quarter} \
  AND #{Date.end_of_quarter}"
)

Again, not perfect but at least Brakeman isn’t going to warn about interpolating a string literal into the query.

Not Done Yet

Ah, but wait. The warning is not gone!

Confidence: Medium
Category: SQL Injection
Check: SQL
Message: Possible SQL injection
Code: Task.connection.select_all("SELECT COUNT(*)\nFROM `filings`\nWHERE `filings`.`status` NOT IN (#{"BRAKEMAN_SAFE_LITERAL,BRAKEMAN_SAFE_LITERAL"})\nAND `filings`.`time_end` BETWEEN #{Date.beginning_of_quarter} AND #{Date.end_of_quarter}\n")
File: app/models/task.rb
Line: 25

What’s wrong now?

Brakeman doesn’t know what Date.beginning_of_quarter or Date.end_of_quarter are, so it generates a lower confidence warning about it. For SQL injection, Brakeman is pretty paranoid about any string interpolation, even if it’s not sure the values are “dangerous”.

But anything coming from Date is likely to be safe, so now Brakeman ignores Date calls in SQL.

Whew. Done?

Yep - now that code will no longer warn.

Except… in the months it took me to address this false positive, the code has changed in such a way that Brakeman now has a false negative problem (should be warning about some things, but isn’t). But that’s a different problem for a different day.

At least that one false positive is fixed!