Today I wanted to streamline the code in the activate
method for the entities that apply the advance
command. I started by breaking the code in the series of if
statements out into separate methods, which at least makes things look cleaner.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
function errorUndefinedAdvanceState(id,state) { //alert("entity "+id+" went into undefined state"); displayProgressText("entity "+id+" in undefined state: "+state+", at time "+globalSimClock+"<br />"); } //entity item 2 function entity2(initialTime,initialPosition,speed,endPosition) { this.entityID = getNewID(); this.initialTime = initialTime; this.currentPosition = initialPosition; this.speed = speed; this.endPosition = endPosition; this.nextState = "go_forward"; this.waitCountdown = 5; feq.newItem(initialTime,this); displayProgressText("entity "+this.entityID+" created at time "+globalSimClock+"<br />"); this.go_forward = function() { displayProgressText("entity "+this.entityID+" updated at time "+globalSimClock+" position: "+this.currentPosition+"<br />"); this.currentPosition += this.speed; if (this.currentPosition >= this.endPosition) { if (this.currentPosition > this.endPosition) { this.currentPosition = this.endPosition; } this.nextState = "wait"; } advance(7.01); } this.wait = function() { this.waitCountdown--; displayProgressText("entity "+this.entityID+" waiting at time "+globalSimClock+" wait count: "+this.waitCountdown+"<br />"); if (this.waitCountdown <= 0.0) { this.nextState = "destroy"; } advance(7.01); } this.destroy = function() { displayProgressText("entity "+this.entityID+" terminated at time "+globalSimClock+"<br />"); } this.activate = function() { bumpGlobalExecutionCount(); if (this.nextState == "go_forward") { this.go_forward(); } else if (this.nextState == "wait") { this.wait(); } else if (this.nextState == "destroy") { this.destroy(); } else { errorUndefinedAdvanceState(this.entityID,this.nextState); } }; //this.activate }; //entity2 |
I defined a standard function called errorUndefinedAdvanceState
so I don’t have to continually redefine it for the default/error condition, but the fact that it’s external to the object means that you have to pass the identifying parameters in to it.
I also observed that using strings for the governing state variable is unnecessarily slow, though it does provide information for the programmer. If these were replaced by variables with similar names that were assigned numeric values (e.g., 0, 1, 2, etc…), then the operations would be much quicker.
My next thought was to build some sort of hash table that could be used by yet another standard, external function, but then you’d have to build the hash, process it externally, and possibly incur extra overhead and weirdness. If you’re going to include overhead that can’t be hidden from the programmer via a compiler which handles such mechanisms in the background, you might as well keep it as clear and simple as you can.
I then read up on the relative merits of compound if
statements and switch
statements. You can Google the discussion for yourself, but the most interesting finding was the idea of declaring an array of functions (see a decent discussion of the idea here), which are then called using the numeric state variable as an array reference.
This works great in theory, and I tried it by modifying the code above.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
//entity item 2a function entity2a(initialTime,initialPosition,speed,endPosition) { var go_forward_ = 0; var wait_ = 1; //define named index values for clarity var destroy_ = 2; this.entityID = getNewID(); this.initialTime = initialTime; this.currentPosition = initialPosition; this.speed = speed; this.endPosition = endPosition; this.nextState = go_forward_; this.waitCountdown = 5; feq.newItem(initialTime,this); displayProgressText("entity "+this.entityID+" created at time "+globalSimClock+"<br />"); this.go_forward = function(entity) { displayProgressText("entity "+this.entityID+" updated at time "+globalSimClock+" position: "+this.currentPosition+"<br />"); this.currentPosition += this.speed; if (this.currentPosition >= this.endPosition) { if (this.currentPosition > this.endPosition) { this.currentPosition = this.endPosition; } this.nextState = wait_; } advance(7.01); } this.wait = function() { this.waitCountdown--; displayProgressText("entity "+this.entityID+" waiting at time "+globalSimClock+" wait count: "+this.waitCountdown+"<br />"); if (this.waitCountdown <= 0.0) { this.nextState = destroy_; } advance(7.01); } this.destroy = function() { displayProgressText("entity "+this.entityID+" terminated at time "+globalSimClock+"<br />"); } //define the array of functions here this.activateArray = [this.go_forward,this.wait,this.destroy]; this.activate = function() { bumpGlobalExecutionCount(); if ((this.nextState >= 0) && (this.nextState <= 2)) { //call the desired function this way this.activateArray[this.nextState](); } else { errorUndefinedAdvanceState(this.entityID,this.nextState); } }; //this.activate }; //entity2a |
This method works…sort of–but not really. The problem stems from all of the properties and methods being inside an object (technically a closure). The mechanism calls the proper subroutine, but has lost track of the context, which means it can’t properly refer to the various properties (e.g., this.entityID
) correctly. The problem has to do with the this
keyword. The this
keyword is supposed to identify the owner of the item in question, which usually means an object. In this case however, we’ve assigned the function to an array, and when we call it the context of the this
is (most annoyingly) the array, and not the parent object. I tried some things like dereferencing using foolishness like this.this
, using external variables to hold the correct context, and so on, but nothing seemed to work. I thought about using anonymous functions but that seemed to take me back to where I started. I like this idea, even if it only makes sense for cases where there are many options, but I’ll have to do some more research.