June 25, 2014

Swift, Objects, Arrays, and Simulation

Posted in iPhone development, mac development, simulation tagged , , , , at 9:24 pm by tetontech

One of the tasks I give myself when learning a new language is to write a traditionally designed discrete event simulation engine. This is due to my background in creating and supporting simulation software for the semi-conductor industry. Another reason is that it gives me a frame of reference I’m comfortable with and allows me to focus on how the different parts of the new language interact with each other.

To highlight certain Swift concepts and how they can interact I’ll go over some of my simulation engine’s code. The entire code set is available on gitHub in the SimSwift-Traditional repository. It includes 3 files – Engine.swift, Event.swift, and ViewController.swift.

The simplest file is Event.swift so let’s start there. It describes the Event class, its attributes, and its behaviors.

The Event class has two constant properties, an integer that represents the time that the event should occur, and a closure or function that is the action to be taken at that time. The trigger time is used by the engine to sort and group events that need to happen. I chose Int as the datatype to allow maximal flexibility for the engine. Time in the engine could be milliseconds since the unix epoch time. It could also be zero based and have each event trigger at some number of seconds, milliseconds, hours, days, or some other offset from the beginning of the simulation.

 24 import Foundation
 25 
 26 class Event{
 27     let triggerTime:Int
 28     let action:()->()
 29 
 30     init(aTriggerTime:Int, anAction:()->()){
 31         triggerTime = aTriggerTime
 32         action = anAction
 33     }
 34 }

The Event class includes an init method. Swift classes need to have init methods to set properties. If you don’t declare an init method Swift will not generate a default at compile time in most cases. In the init method  the parameters are assigned to their matching properties. You also see that the parameters are named parameters. This means that when init is called the parameter names are used to delimit the parameters. I’ll show you how to use the delimiters when we look at some of the code in the ViewController.swift file.

The simulation engine class, TraditionalEngine, has three variable properties. The time property represents the ‘current’ time of the simulation, futures are events that need to happen at some future time, and presents are events that have trigger times that match the ‘current’ time of the simulation.

class TraditionalEngine{
    
    var time:Int
    var futures:Array<Event>
    var presents:Array<Event>
    
    init(startTime:Int){
        time = startTime
        futures = Array<Event>()
        presents = Array<Event>()
    }

The init method for the TraditionalEngine class has one named parameter, startTime. startTime represents the initial time of the simulation. The futures and presents arrays are set to empty arrays so events can be added later.

I’ve given the engine an addEvent method. It has one named parameter, an. One purpose of the addEvent method is to determine if the event should be added to the presents or the futures array. Which it is added to depends on if the event’s triggerTime is after the current time of the simulation.

    func addEvent(an:Event){
        an.triggerTime > time ? insertEvent(an, isFuture: true)
                                     : insertEvent(an, isFuture: false)
    }

I chose to use Swift’s ternary operator ( ? : ) for reasons of succinctness. If the event’s trigger time is greater than the simulation’s current time the event is inserted into, not appended to, the futures array, otherwise it is inserted into the presents array. In either case a binary search insertion is performed.

Why do insertion in the presents list? From industry experience I know that events users setup to start simulations sometimes happened in the past. By using an insertion based approach, events in the past will be triggered before the current ones. Let’s look at the code that does the insertion later. First we’ll take a look at the simulation’s run loop found in the engine’s go method.

While there are events waiting in the future or events for the current time the simulation loop continues. In the loop any present events are triggered and then the next group of events are found, executed, and the loop continues. This is the heart of any traditionally designed simulation engine.

    func go(){
        while(self.futures.count > 0 || self.presents.count > 0){
            self.doAnyPresents()
            self.doNextGroup()
        }
    }
    
    func doAnyPresents(){
        while presents.count > 0 {
            presents.removeAtIndex(0).action()
        }
    }
    
    func doNextGroup(){
        if futures.count > 0 {
            let nextPresent = futures.removeAtIndex(0)
            self.time = nextPresent.triggerTime
            nextPresent.action()
            while futures.count > 0 
                  && futures[0].triggerTime == self.time{
                futures.removeAtIndex(0).action()
            }
        }
    }

The doNextGroup method pops the first event off the futures array and sets the simulation’s current time to the popped event’s triggerTime. This is what causes time to move forward in the simulation. Thankfully, it also means we don’t need to simulate the time between events when nothing is happening. The doNextGroup method executes all events that match the current time.

Why then does the simulation loop always call doAnyPresents before calling doNextGroup? In discrete event simulations it is common for events to create new events that have the same trigger time as the current time. Rather than make the programmer using the simulation’s API write if statements all over in their code they can use the addEvent method and not have to worry about which Array to add it to. If the event has a trigger time that matches the simulation’s current time it will end up in the currents Array and be executed before the simulation moves on to the next group of events.

Rather than put a code listing for the entire insertEvent method please go view it or download the source from the gitHub repository. It is a standard binary search for an insertion point. To minimize computation, a check is done before doing the binary search. If the trigger time of the event being added is less than the first event’s trigger time the event is prepended. If it is greater than the last event’s trigger time the event is appended. If it is somewhere in-between the binary search process is executed. See the source from gitHub to for the full implementation of the binary insertion algorithm.

One part of the implementation is interesting. To avoid the array copy problem explained in a previous post, the engine’s list properties are modified directly rather than trough a facade-like approach.

isFuture ? futures.insert(an, atIndex: midIndex) 
         : presents.insert(an, atIndex: midIndex)

This line of code is longer and less expressive than I would like it to be but it does avoid the copy problem.

There is some sample code in the ViewController class that shows the engine’s API being used. In it a TraditionalEngine is instantiated and stored as a parameter of the ViewController. 0 is used as the start time of the simulation so all event triggerTimes are considered to be time offsets. In viewDidLoad 50 random integers from 0 – 100 are created and if any of them are a multiple of 15 they are made negative. This will exercise the code handling the situation where the simulation has events to process from before the beginning of the simulation.

class ViewController: UIViewController {
    let theEngine = TraditionalEngine(startTime:0)
                            
    override func viewDidLoad() {
        super.viewDidLoad()
        
        for index in 0..50{
            var randNum = Int(arc4random() % 100)
            if randNum % 15 == 0 {
                randNum = -randNum
            }
            let capturedNum = Int(arc4random() % 50)
            theEngine.addEvent(Event(aTriggerTime: randNum, anAction: {
                println("triggered at: \(randNum) with 
                                   captured: \(capturedNum)")
                }))
        }
        theEngine.go()
    }
}

I’ve used a closure as the action for the events. I chose a closure to show how a constant or variable could be captured and used later when the event’s action is triggered.

All in all, Swift worked quite well for creating a traditionally designed simulation engine.

Advertisements

2 Comments »

  1. samjbarney said,

    I don’t know if this is a problem for everyone, but the sections of code that you have written out get cut off for me. For example, the ‘func addEvent’ section gets cut off right before the second insertEvent call.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: