Managing Lots of Cars

The demonstration code for the I35 simulator only showed about 20 cars cruising down our four mile stretch of highway. The “real” situation has many more cars than that on the road. What is the maximum capacity of our road?

Austin Rush Hour

To figure this out, we need some data!

I35 though downtown Austin is three lanes wide. We are simulating about four miles of that highway. That works out to 21120 feet of vehicles.

According to Google, cars are about 16 feet long, so we can fit 1320 cars per lane. Times three gives us a maximum of 3960 vehicles (all sitting on the road going nowhere.

Trucks average about 75 feet long, 51 feet for the trailer, and 22 for the cab. If the highway was full of trucks, we could fit a maximum of 846 trucks. During rush-hour (rush-day?) There seem to be as many trucks as cars, so we can have around 700 cars and 700 trucks will fit on our road.

Note

Have you noticed the tactic many semi drivers use to get through rush-hour? The idle their way through the mess allowing a big gap ahead of them to open up, which cars zip into. The truck never slows down and traffic moves along around it. The truck drivers are probably checking their facebook pages. (Nah, most truck drivers are actually far better drivers than those silly folks in cars around Austin!)

The point of this discussion is that we have to manage a lot of cars and trucks, and our scheme for detecting collisions, or figuring out who is ahead or behind any give car, is going to be slow if we just search a random list of vehicles.

A Sorted Affair

Maybe it would be better to maintain our vehicles in a sorted list. We might not even need to worry about the lane each vehicle is in, just their location along the highway. When it is time to check for collisions with nearby vehicles, they will be very close to the chosen vehicle in the list, and all we need to do is analyze a short set of vehicles ahead and behind us.

Managing an sorted array of objects requires moving all the objects around as we slide the objects back and forth to open up a slot for them. Managing a linked list of vehicles requires rearranging several pointers, but no big data movements.

Think about what we are simulating. All the vehicles are moving in the same direction, so what is going to happen is that a vehicle is going to pass another one in the list, and we need to exchange those two vehicles only. Actually, we need to be careful doing this, because with three lanes, a vehicle passing one vehicle, might pass up to three at the same time.

Still this is going to be much faster than searching a random list for “neighboring” vehicles.

So, how do we manage the list.

Game developers manage not hundreds of objects but thousands of them. And they worry about the performance of accessing those data items.

The Memory Cache

All modern processors have a small amount of really fast memory inside the chip. This memory is called a cache, and it can speed up your program significantly, if you manage your data correctly!

When the processor needs to grab something from RAM, which is pretty slow compared to the processor, it does not actually go to RAM all the time. Instead, when it does read from memory, it reads in a block of locations and stores that block in the fast memory cache. On subsequent fetches of data, the odds are good that the data you will ask for is already in the cache, and the processor can get that data much faster. This is certainly tru if your data is in arrays.

However, the cache is only so big, so eventually, the data required will not be there, so the current cache is thrown out and a new block gets loaded in its place.

If all of your data is sitting in an array, guaranteed to be stored in sequential memory locations, the cache can really speed up calculations. However, if you randomly jump around in your array, cache performance can fall apart, and you program gets slow.

Note

Some gaming developers even recomend not storing all the data in objects, then putting those objects in an array. Instead, they put individual attributes in parallel arrays. The array index is what makes the data part of a single “object in this case. Cache performance goes up when you do this.

This tactic was common in the early days of programming, but is often overlooked in todays heavy focus on objects!

We do not really know if our simulation will slow down as we fill it up with a thousand or so vehicles. The only way to tell is to try it and see. If it is too slow, we might need to reevaluate our management scheme.

Time for An Experiment

Lets keep our vehicle objects in a sorted array with a set maximum size. (We could allocate a user defined maximum number, but this is fine for this experiment.

We will keep the array sorted, which means when we have a new vehicle to place in the array, we need to find the place where it goes, then move other cars around so we can properly position that vehicle.

As passes happen, one car will only be passing a small number of vehicles ahead of that one in the list. (We do not need to worry about cars slowing down. All that means is that other cars will be doing the passing!)

Let’s start off by building a new class, this one named Traffic. This class will be the primary storage container for our simulation objects.

Here is a start on the specification for this class:

lib/Traffic.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// copyright 2018 Roie R. Black
#pragma once
#include "Vehicle.h"

const int MAX_VEHICLES = 1500;

class Traffic {
 public:
    // constructor
    Traffic();

    // accessors
    Vehicle * get_cars(void);
    int get_size();

    // mutators
    void pass(int c, float dist); 
    void insert(void);
    void retire(int c);

 private:
    int size;
    Vehicle vehicles[MAX_VEHICLES];
};