├── .hintrc ├── simscript ├── includes │ ├── entity.md │ ├── queue.md │ ├── random.md │ ├── animation.md │ └── simulation.md ├── docs │ ├── assets │ │ └── images │ │ │ ├── icons.png │ │ │ ├── widgets.png │ │ │ ├── icons@2x.png │ │ │ └── widgets@2x.png │ ├── interfaces │ │ ├── ieventlistener.html │ │ ├── ianimationposition.html │ │ ├── ihistogramentry.html │ │ ├── ipoint.html │ │ └── inode.html │ ├── classes │ │ └── eventargs.html │ └── enums │ │ └── simulationstate.html ├── publish.bat ├── index.ts ├── tsconfig.json ├── package.json ├── simscript.css ├── event.ts ├── util.ts ├── tally.ts ├── README.md └── network.ts ├── resources ├── car.png ├── ped.png ├── won.mp3 ├── lost.mp3 ├── redcar.png ├── thrust.mp3 ├── blueped.png ├── missile.mp3 ├── explosion.mp3 └── car and pedestrian.png ├── package.json ├── tsconfig.json ├── .vscode └── launch.json ├── LICENSE ├── favicon.svg ├── simulations ├── barbershop.ts ├── promise-all.ts ├── mmc.ts ├── animation-options.ts ├── multiserver.ts ├── crosswalk.ts ├── randomvartest.ts ├── car-follow.ts ├── network-steering.ts ├── simpletest.ts └── car-follow-network.ts ├── .gitignore ├── index.html ├── todo.txt ├── style.css └── README.md /.hintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "development" 4 | ] 5 | } -------------------------------------------------------------------------------- /simscript/includes/entity.md: -------------------------------------------------------------------------------- 1 | /** 2 | * [[include:entity.md]] 3 | */ 4 | -------------------------------------------------------------------------------- /simscript/includes/queue.md: -------------------------------------------------------------------------------- 1 | /** 2 | * [[include:queue.md]] 3 | */ 4 | -------------------------------------------------------------------------------- /simscript/includes/random.md: -------------------------------------------------------------------------------- 1 | /** 2 | * [[include:random.md]] 3 | */ 4 | -------------------------------------------------------------------------------- /simscript/includes/animation.md: -------------------------------------------------------------------------------- 1 | /** 2 | * [[include:animation.md]] 3 | */ 4 | -------------------------------------------------------------------------------- /simscript/includes/simulation.md: -------------------------------------------------------------------------------- 1 | /** 2 | * [[include:simulation.md]] 3 | */ 4 | -------------------------------------------------------------------------------- /resources/car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bernardo-Castilho/SimScript/HEAD/resources/car.png -------------------------------------------------------------------------------- /resources/ped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bernardo-Castilho/SimScript/HEAD/resources/ped.png -------------------------------------------------------------------------------- /resources/won.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bernardo-Castilho/SimScript/HEAD/resources/won.mp3 -------------------------------------------------------------------------------- /resources/lost.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bernardo-Castilho/SimScript/HEAD/resources/lost.mp3 -------------------------------------------------------------------------------- /resources/redcar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bernardo-Castilho/SimScript/HEAD/resources/redcar.png -------------------------------------------------------------------------------- /resources/thrust.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bernardo-Castilho/SimScript/HEAD/resources/thrust.mp3 -------------------------------------------------------------------------------- /resources/blueped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bernardo-Castilho/SimScript/HEAD/resources/blueped.png -------------------------------------------------------------------------------- /resources/missile.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bernardo-Castilho/SimScript/HEAD/resources/missile.mp3 -------------------------------------------------------------------------------- /resources/explosion.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bernardo-Castilho/SimScript/HEAD/resources/explosion.mp3 -------------------------------------------------------------------------------- /resources/car and pedestrian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bernardo-Castilho/SimScript/HEAD/resources/car and pedestrian.png -------------------------------------------------------------------------------- /simscript/docs/assets/images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bernardo-Castilho/SimScript/HEAD/simscript/docs/assets/images/icons.png -------------------------------------------------------------------------------- /simscript/docs/assets/images/widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bernardo-Castilho/SimScript/HEAD/simscript/docs/assets/images/widgets.png -------------------------------------------------------------------------------- /simscript/docs/assets/images/icons@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bernardo-Castilho/SimScript/HEAD/simscript/docs/assets/images/icons@2x.png -------------------------------------------------------------------------------- /simscript/docs/assets/images/widgets@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bernardo-Castilho/SimScript/HEAD/simscript/docs/assets/images/widgets@2x.png -------------------------------------------------------------------------------- /simscript/publish.bat: -------------------------------------------------------------------------------- 1 | copy ..\readme.md 2 | copy simscript.css dist 3 | call tsc 4 | call typedoc index.ts 5 | 6 | echo update the version in package.json, then 'npm publish' 7 | echo npm run build 8 | echo update the links in \simscript\dist\index.html: ** should be './xxx', not '/xxx' 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-project", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "serve": "vite preview" 8 | }, 9 | "devDependencies": { 10 | "vite": "^2.1.5" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /simscript/index.ts: -------------------------------------------------------------------------------- 1 | export * from './simulation'; 2 | export * from './entity'; 3 | export * from './queue'; 4 | export * from './tally'; 5 | export * from './random'; 6 | export * from './event'; 7 | export * from './animation'; 8 | export * from './network'; 9 | export * from './util'; 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "module": "ES2015", 5 | "target": "ES2015", 6 | "declaration": true, 7 | "outDir": "./dist", 8 | "strictNullChecks": false, 9 | "stripInternal": true, 10 | }, 11 | "typedocOptions": { 12 | "name": "SimScript", 13 | "entryPoints": ["./index.ts"], 14 | "out": "./simscript/docs", 15 | "excludePrivate": true, 16 | "excludeInternal": true, 17 | "theme": "minimal" 18 | } 19 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "pwa-chrome", 9 | "request": "launch", 10 | "name": "Launch Chrome against localhost:3000", 11 | "url": "http://localhost:3000", 12 | "webRoot": "${workspaceFolder}" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /simscript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2015", 5 | "declaration": true, 6 | "removeComments": true, 7 | "outDir": "./dist" 8 | }, 9 | "include": [ 10 | "*.ts" 11 | ], 12 | "typedocOptions": { 13 | "entryPoints": [ 14 | "index.ts" 15 | ], 16 | "out": "docs", 17 | "includes": "./includes", 18 | "excludePrivate": true, 19 | "excludeInternal": true, 20 | "excludeProtected": true, 21 | } 22 | } -------------------------------------------------------------------------------- /simscript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simscript", 3 | "version": "1.0.37", 4 | "description": "A Discrete Event Simulation Library in TypeScript", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "files": [ 8 | "/dist" 9 | ], 10 | "keywords": [ 11 | "Discrete Event Simulation", 12 | "Simulation", 13 | "Animation", 14 | "X3DOM", 15 | "A-Frame", 16 | "Network" 17 | ], 18 | "author": "Bernardo Castilho", 19 | "license": "ISC", 20 | "scripts": { 21 | "test": "echo \"No test specified\"" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/Bernardo-Castilho/SimScript.git" 26 | }, 27 | "bugs": { 28 | "url": "https://github.com/Bernardo-Castilho/SimScript/issues" 29 | }, 30 | "homepage": "https://github.com/Bernardo-Castilho/SimScript#readme", 31 | "dependencies": { 32 | "typedoc": "^0.20.36", 33 | "typescript": "^4.2.4" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Bernardo Castilho 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /simscript/simscript.css: -------------------------------------------------------------------------------- 1 | /* stats tables */ 2 | table.ss-stats { 3 | margin-bottom: 1em; 4 | } 5 | 6 | table.ss-stats th { 7 | font-weight: bold; 8 | } 9 | 10 | table.ss-stats th:not([colspan]):first-child { 11 | text-align: left; 12 | padding-left: 2em; 13 | } 14 | table.ss-stats th[colspan] { 15 | text-align: left; 16 | padding-top: 0.5em; 17 | } 18 | 19 | table.ss-stats td { 20 | margin: 0 1em; 21 | } 22 | 23 | table.ss-stats tr, 24 | table.ss-stats td { 25 | text-align: right; 26 | padding-left: 2em; 27 | } 28 | 29 | /* histograms */ 30 | figure.ss-histogram { 31 | padding: 0 1em; 32 | margin: 1em 0; 33 | } 34 | 35 | figure.ss-histogram figcaption { 36 | text-align: center; 37 | font-weight: bold; 38 | margin-bottom: .5em; 39 | } 40 | 41 | figure.ss-histogram svg text { 42 | font-size: 80%; 43 | } 44 | 45 | figure.ss-histogram svg text.avg { 46 | font-weight: bold; 47 | } 48 | 49 | figure.ss-histogram svg rect { 50 | fill: rgb(0,100,255); 51 | opacity: 0.5; 52 | } 53 | 54 | figure.ss-histogram svg rect.avg { 55 | opacity: 1; 56 | } 57 | 58 | /* animation transforms */ 59 | .ss-anim .ss-entity { 60 | transform-origin: 50% 50%; 61 | transform-box: fill-box; 62 | } 63 | 64 | /* charts */ 65 | svg.ss-chart { 66 | display: block; 67 | width: 100%; 68 | height: 250px; 69 | } 70 | -------------------------------------------------------------------------------- /favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /simulations/barbershop.ts: -------------------------------------------------------------------------------- 1 | import { Simulation } from '../simscript/simulation'; 2 | import { Entity } from '../simscript/entity'; 3 | import { Queue } from '../simscript/queue'; 4 | import { Uniform } from '../simscript/random'; 5 | 6 | // https://try-mts.com/gpss-introduction-and-barber-shop-simulation/ 7 | export class BarberShop extends Simulation { 8 | qJoe = new Queue('Joe', 1); 9 | qWait = new Queue('Wait Area'); 10 | 11 | // generate entities with inter-arrival times of 18 min for 8 hours * 7 days 12 | onStarting() { 13 | super.onStarting(); 14 | this.timeUnit = 'min'; 15 | this.timeEnd = 60 * 8 * 7; // 8 hours * 7 days 16 | this.qWait.grossDwell.setHistogramParameters(1); 17 | this.generateEntities(Customer, new Uniform(18 - 6, 18 + 6)); // arrivals every ~18min 18 | } 19 | } 20 | class Customer extends Entity { 21 | service = new Uniform(15 - 3, 15 + 3); // service takes ~15min 22 | async script() { 23 | const sim = this.simulation; 24 | if (true) { // compact version: using seize 25 | await this.seize(sim.qJoe, this.service.sample(), sim.qWait); 26 | } else { // explicit version: using enterQueue/delay/leaveQueue 27 | await this.enterQueue(sim.qWait); // enter the line 28 | await this.enterQueue(sim.qJoe); // seize Joe the barber 29 | this.leaveQueue(sim.qWait); // leave the line 30 | await this.delay(this.service.sample()); // get a haircut 31 | this.leaveQueue(sim.qJoe); // free Joe 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /simulations/promise-all.ts: -------------------------------------------------------------------------------- 1 | import { Simulation } from '../simscript/simulation'; 2 | import { Queue } from '../simscript/queue'; 3 | import { Entity } from '../simscript/entity'; 4 | import { Exponential } from '../simscript/random'; 5 | 6 | export class PromiseAll extends Simulation { 7 | delay = new Exponential(100); 8 | qWork = new Queue('work', 1); 9 | qWait = new Queue('wait'); 10 | onStarting() { 11 | super.onStarting(); 12 | this.activate(new PromiseAllEntityMain()); 13 | } 14 | } 15 | class PromiseAllEntityMain extends Entity { 16 | async script() { 17 | 18 | // create some child-tasks and run them all 19 | console.log('creating 5 child-tasks at', this.simulation.timeNow); 20 | let all = await Promise.all([ 21 | this.simulation.activate(new PromiseAllEntityChild()), 22 | this.simulation.activate(new PromiseAllEntityChild()), 23 | this.simulation.activate(new PromiseAllEntityChild()), 24 | this.simulation.activate(new PromiseAllEntityChild()), 25 | this.simulation.activate(new PromiseAllEntityChild()), 26 | ]); 27 | console.log('all child-tasks done at', this.simulation.timeNow); 28 | } 29 | } 30 | class PromiseAllEntityChild extends Entity { 31 | async script() { 32 | const sim = this.simulation; 33 | console.log(this.serial, 'child starting at', this.simulation.timeNow); 34 | await this.seize(sim.qWork, sim.delay.sample(), sim.qWait); 35 | console.log(this.serial, 'child done at', this.simulation.timeNow); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /simulations/mmc.ts: -------------------------------------------------------------------------------- 1 | import { Simulation } from '../simscript/simulation'; 2 | import { Entity } from '../simscript/entity'; 3 | import { Queue } from '../simscript/queue'; 4 | import { Exponential } from '../simscript/random'; 5 | 6 | // MMC simulation 7 | export class MMC extends Simulation { 8 | qWait = new Queue('Wait'); 9 | qService = new Queue('Service', 2); 10 | interArrival = new Exponential(80); 11 | service = new Exponential(100); 12 | 13 | // generate entities with exponential inter-arrival times 14 | onStarting() { 15 | super.onStarting(); 16 | this.timeUnit = 'min'; 17 | 18 | // get up tally histograms 19 | this.qWait.grossPop.setHistogramParameters(1, 0, 10); 20 | this.qWait.grossDwell.setHistogramParameters(60, 0, 500 - 0.1); 21 | 22 | // start simulation 23 | this.generateEntities(Customer, this.interArrival, 100000); // limit the # of customers 24 | } 25 | } 26 | 27 | // customer 28 | class Customer extends Entity { 29 | async script() { 30 | let sim = this.simulation; 31 | 32 | if (sim.qWait.canEnter()) { 33 | this.enterQueueImmediately(sim.qWait); // faster (no await) 34 | } else { 35 | await this.enterQueue(sim.qWait); // same thing, but slower 36 | } 37 | 38 | if (sim.qService.canEnter()) { 39 | this.enterQueueImmediately(sim.qService); // faster (no await) 40 | } else { 41 | await this.enterQueue(sim.qService); // same thing, but slower 42 | } 43 | 44 | this.leaveQueue(sim.qWait); 45 | await this.delay(sim.service.sample()); 46 | this.leaveQueue(sim.qService); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | SimScript 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |

18 | SimScript 19 |

20 |

21 | A Discrete Event Simulation library in TypeScript. 22 |

23 |

24 | SimScript uses JavaScript's 25 | async/await 26 | features to make simulations easy to write and understand. 27 |

28 |

29 | SimScript simulations are built using these classes: 30 |

31 |
    32 |
  • 33 | Simulation: 34 | Simulations create resources (queues) and entities which execute an async script 35 | method that describes the actions each entity should perform. 36 |
  • 37 |
  • 38 | Queues: 39 | Queues represent resources that can be seized and released by entities. 40 | Queues keep track of their utilization and may constrain the flow of entities through 41 | the simulation. 42 |
  • 43 |
  • 44 | Entities: 45 | Entities represent active elements that execute scripts. Scripts are JavaScript methods that 46 | contain instructions for entities. Typical actions are entering and leaving queues, 47 | going through delays, and sending or waiting for signals. 48 |
  • 49 |
  • 50 | Animations: 51 | Animations use HTML or SVG elements to render entities waiting in queues or in transit 52 | between queues. 53 | Animations are useful for presentations and also for debugging simulations. 54 |
  • 55 |
56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /simulations/animation-options.ts: -------------------------------------------------------------------------------- 1 | import { Simulation } from '../simscript/simulation'; 2 | import { Entity } from '../simscript/entity'; 3 | import { Queue } from '../simscript/queue'; 4 | import { Uniform } from '../simscript/random'; 5 | import { setOptions } from '../simscript/util'; 6 | 7 | export class AnimationOptions extends Simulation { 8 | qAngle = 270; 9 | qRotate = new Queue('Rotate'); 10 | qCenter = new Queue('qcenter') 11 | q1 = new Queue('q1'); 12 | q2 = new Queue('q2'); 13 | q3 = new Queue('q3'); 14 | q4 = new Queue('q4'); 15 | q5 = new Queue('q5'); 16 | q6 = new Queue('q6'); 17 | q7 = new Queue('q7'); 18 | q8 = new Queue('q8'); 19 | q9 = new Queue('q9'); 20 | q10 = new Queue('q10'); 21 | q11 = new Queue('q11'); 22 | q12 = new Queue('q12'); 23 | splineTension = 0.5; 24 | interArrival = new Uniform(5, 10); 25 | moveDelayLong = new Uniform(50, 200); 26 | moveDelayShort = new Uniform(30, 60); 27 | 28 | onStarting() { 29 | super.onStarting(); 30 | 31 | // create some entities to enter the rotating queue 32 | for (let i = 0; i < 6; i++) { 33 | this.activate(new RotatingEntity()); 34 | } 35 | 36 | // create some entities to roam around 37 | this.generateEntities(RoamEntity, this.interArrival, 20); 38 | } 39 | } 40 | 41 | export class RotatingEntity extends Entity { 42 | async script() { 43 | this.enterQueueImmediately(this.simulation.qRotate); 44 | await this.waitSignal(null); // wait forever 45 | } 46 | } 47 | 48 | export class RoamEntity extends Entity { 49 | fast = false; 50 | 51 | constructor(options?: any) { 52 | super(null); // in case options includes 'fast' 53 | this.fast = this.serial % 2 == 0; 54 | setOptions(this, options); 55 | } 56 | 57 | async script() { 58 | let sim = this.simulation; 59 | for (; ;) { 60 | const moveDelay = this.fast 61 | ? sim.moveDelayShort // fast entity 62 | : sim.moveDelayLong; // slow entity 63 | 64 | await this.delay(moveDelay.sample(), { 65 | queues: [ 66 | sim.qCenter, 67 | sim.q1, sim.q2, sim.q3, sim.q4, sim.q5, 68 | sim.q11, sim.q10, sim.q9, sim.q8, sim.q7, 69 | sim.qCenter 70 | ], 71 | tension: sim.splineTension 72 | }); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | # Todo 2 | * Simulation.stop(reset?) 3 | - SVG transform/network 4 | - too many WebGL contexts (X3DOM) 5 | - npm run dev 6 | - npm run build 7 | - npm run serve 8 | 9 | * steering behaviors (http://www.red3d.com/cwr/steer/) 10 | * react samples 11 | - code project article 12 | discrete event simulation 13 | scheduler, await/async 14 | typescript, c# 15 | Simulation (stage: controls the show) 16 | Entities (actors: move through the simulation) 17 | Queues (props: restrict entity flow, gather stats) 18 | Generate Entities 19 | Restrict Entity Flow: delay, enterQueue/leaveQueue, wait/signal 20 | Collect Stats: queues, histograms 21 | examples: barbershop, mmc, crosswalk 22 | animation: 2d, 3d 23 | - more docs/examples 24 | - pac-man 25 | - elevators 26 | - robot/warehouse (3d) 27 | - strips 28 | - length, max speed, vehicle list (with positions: extends queue) 29 | - multi-segment 30 | - multi-lane 31 | - rail (one lane, one direction) 32 | - road (multi-lane, one direction) 33 | - canals? 34 | 35 | # Change Log 36 | 37 | SimScript 1.0.36 (9/2/21) 38 | - Fixed Animation class to reload X3DOM when needed. 39 | - Fixed bug in Animation class bounding box calculations. 40 | - Improved React sample. 41 | 42 | SimScript 1.0.35 (8/29/21) 43 | - Added 'reset' parameter to **Simulation.stop** method. 44 | - Added React sample (with routing etc). 45 | 46 | SimScript 1.0.34 (8/16/21) 47 | - Improved documentation 48 | - Improved "AvoidBehavior" implementation and samples. 49 | 50 | SimScript 1.0.33 (8/12/21) 51 | - Improved documentation 52 | - Improved "AvoidBehavior" samples. 53 | 54 | SimScript 1.0.32 (8/10/21) 55 | - Added **Animation.updateEntity** property to update the state of animated enitities. 56 | - Improved "Custom Steering Behaviors" samples. 57 | 58 | SimScript 1.0.31 (8/4/21) 59 | - Improved "Custom Steering Behaviors" samples. 60 | 61 | SimScript 1.0.30 (7/28/21) 62 | - Added **Simulation.name** and **Simulation.timeUnit** properties 63 | (used by the **Simulation.getStatsTable** method). 64 | - Allow using zero as an inter-arrival value for the **Simulation.generateEntities** method. 65 | - Improved the behavior of the **Simulation.start(true)** method. 66 | - Added "Custom Steering Behavior" samples. 67 | 68 | SimScript 1.0.29 (7/9/21) 69 | - Added **Entity.seize** method (condenses enterQueue/delay/leaveQueue and provides preemptive behavior). 70 | 71 | SimScript 1.0.27 (7/9/21) 72 | - Improved the format of the tables created by the **Simulation.getStatsTable** method. 73 | - Added **Erlang** and **Gamma** random vars. 74 | - Added generic **Simulation** parameter to **Entity** class (e.g. class MyEnt extends Entity). 75 | - Improved **bind** method to format labels more clearly. 76 | -------------------------------------------------------------------------------- /simscript/event.ts: -------------------------------------------------------------------------------- 1 | export interface IEventListener { 2 | (sender: S, args: T): void; 3 | } 4 | 5 | /** 6 | * Defines a function that is executed when an {@link Event} is raised. 7 | */ 8 | class EventListener { 9 | listener: IEventListener; 10 | self: any; 11 | 12 | /** 13 | * Initializes a new instance of an {@link EventListener}. 14 | * @param listener Function that gets executed when the {@link Event} is raised. 15 | * @param self Object that acts as a **this** within the scope of the listener function. 16 | */ 17 | constructor(listener: IEventListener, self: any) { 18 | this.listener = listener; 19 | this.self = self; 20 | } 21 | } 22 | 23 | /** 24 | * Represents the parameters passed in to listeners attached to an {@link Event}. 25 | */ 26 | export class EventArgs { 27 | static empty = new EventArgs(); 28 | } 29 | 30 | /** 31 | * Represents an event. 32 | * 33 | * Events may have multiple listeners. Each listener is a function that 34 | * gets invoked when the event is raised. 35 | * 36 | * Event listeners are functions that take two parameters: 37 | * **sender** is the object that raised the event, and 38 | * **args** is an object that contains the event parameters. 39 | */ 40 | export class Event { 41 | private _listeners: EventListener[] = []; 42 | 43 | /** 44 | * Sets up a function that gets called whenever the event is raised. 45 | * 46 | * @param listener Function that gets called whenever the event is raised. 47 | * @param self Value returned by the **this** keyword in the context of the 48 | * listener function. 49 | */ 50 | addEventListener(listener: IEventListener, self?: any) { 51 | this._listeners.push(new EventListener(listener, self)); 52 | } 53 | /** 54 | * Removes an event listener so it no longer gets called when the event is raised. 55 | * 56 | * @param listener Event listener to remove. 57 | * @param self Value returned by the **this** keyword in the context of the 58 | * listener function. 59 | */ 60 | removeEventListener(listener: IEventListener, self?: any) { 61 | const listeners = this._listeners; 62 | for (let i = 0; i < listeners.length; i++) { 63 | const l = listeners[i]; 64 | if (l.listener == listener || listener == null) { 65 | if (l.self == self || self == null) { 66 | listeners.splice(i, 1); 67 | if (self) { 68 | break; 69 | } 70 | } 71 | } 72 | } 73 | } 74 | /** 75 | * Raises an {@link Event}, which causes all attached listeners to be 76 | * invoked. 77 | * 78 | * @param sender Object that raised the event. 79 | * @param args {@link EventArgs} object that contains the event parameters. 80 | */ 81 | raise(sender: S, args: T) { 82 | this._listeners.forEach(l => { 83 | l.listener.call(l.self, sender, args); 84 | }); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /simulations/multiserver.ts: -------------------------------------------------------------------------------- 1 | import { Simulation } from '../simscript/simulation'; 2 | import { Entity } from '../simscript/entity'; 3 | import { Queue } from '../simscript/queue'; 4 | import { Exponential, Uniform, LogNormal } from '../simscript/random'; 5 | 6 | const _NUMSERVERS = 10; 7 | const _INTERARR = 10; 8 | const _SERVICE = _INTERARR * _NUMSERVERS * .8; 9 | 10 | export class MultiServer extends Simulation { 11 | qMulti = new Queue('MultiServer', _NUMSERVERS); 12 | qMultiWait = new Queue('MultiServerWait'); 13 | 14 | qSingle: Queue[] = []; 15 | qSingleWait = new Queue('SingleServerWait'); 16 | 17 | qSingleNC: Queue[] = []; 18 | qSingleWaitNC = new Queue('SingleServerWaitNC'); 19 | 20 | interArrival = new Exponential(_INTERARR); 21 | service = new Exponential(_SERVICE); 22 | 23 | constructor(options?: any) { 24 | super(options); 25 | for (let i = 0; i < _NUMSERVERS; i++) { 26 | this.qSingle.push(new Queue(`Single(${i})`, 1)) 27 | this.qSingleNC.push(new Queue(`SingleNC(${i})`, 1)) 28 | } 29 | } 30 | onStarting() { 31 | super.onStarting(); 32 | 33 | // one multi-server resource 34 | this.generateEntities(MultiServerResource, this.interArrival); 35 | 36 | // n single-server resources, choice of least-busy server 37 | this.generateEntities(SingleServerResources, this.interArrival); 38 | 39 | // n single-server resources, random server 40 | this.generateEntities(SingleServerResourcesNoChoice, this.interArrival); 41 | } 42 | } 43 | 44 | // entities that use multiple single-server resources 45 | export class MultiServerResource extends Entity { 46 | async script() { 47 | const sim = this.simulation; 48 | 49 | // enter 50 | await this.enterQueue(sim.qMultiWait); 51 | await this.enterQueue(sim.qMulti); 52 | 53 | // wait 54 | await this.delay(sim.service.sample()); 55 | 56 | // leave 57 | this.leaveQueue(sim.qMulti); 58 | this.leaveQueue(sim.qMultiWait); 59 | } 60 | } 61 | 62 | // entities that use n single-server resources and pick a server that is available 63 | export class SingleServerResources extends Entity { 64 | async script() { 65 | const sim = this.simulation; 66 | await this.enterQueue(sim.qSingleWait); 67 | 68 | // select queue that is not busy 69 | let serviceQueue: Queue; 70 | while (serviceQueue == null) { 71 | for (let i = 0; i < sim.qSingle.length; i++) { 72 | let q = sim.qSingle[i]; 73 | if (q.canEnter()) { 74 | serviceQueue = q; 75 | break; 76 | } 77 | } 78 | if (serviceQueue == null) { 79 | await this.waitSignal(SingleServerResources); 80 | } 81 | } 82 | 83 | // enter the queue immediately (no await needed here) 84 | this.enterQueueImmediately(serviceQueue); 85 | 86 | // wait 87 | await this.delay(sim.service.sample()); 88 | 89 | // leave 90 | this.leaveQueue(serviceQueue); 91 | this.leaveQueue(sim.qSingleWait); 92 | this.sendSignal(SingleServerResources); 93 | } 94 | } 95 | 96 | // entities that use n single-server resources and pick the server at random 97 | export class SingleServerResourcesNoChoice extends Entity { 98 | async script() { 99 | const sim = this.simulation; 100 | await this.enterQueue(sim.qSingleWaitNC); 101 | 102 | // select a random service queue 103 | let i = Math.floor(Math.random() * _NUMSERVERS); 104 | let serviceQueue = sim.qSingleNC[i]; 105 | 106 | // enter 107 | await this.enterQueue(serviceQueue); 108 | 109 | // wait 110 | await this.delay(sim.service.sample()); 111 | 112 | // leave 113 | this.leaveQueue(serviceQueue); 114 | this.leaveQueue(sim.qSingleWaitNC); 115 | } 116 | } -------------------------------------------------------------------------------- /simulations/crosswalk.ts: -------------------------------------------------------------------------------- 1 | import { Simulation } from '../simscript/simulation'; 2 | import { Entity } from '../simscript/entity'; 3 | import { Queue } from '../simscript/queue'; 4 | import { Exponential, Uniform } from '../simscript/random'; 5 | 6 | export enum Signal { 7 | RED, 8 | YELLOW, 9 | GREEN, 10 | } 11 | 12 | // CrossWalk simulation 13 | // time units: seconds 14 | export class Crosswalk extends Simulation { 15 | qPedArr = new Queue('Pedestrian Arrival'); 16 | qPedXing = new Queue('Pedestrian Crossing'); 17 | qPedXed = new Queue('Pedestrian Crossed'); 18 | qPedLeave = new Queue('Pedestrian Leaving'); 19 | 20 | qCarArr = new Queue('Car Arrival'); 21 | qCarXing = new Queue('Car Crossing'); 22 | qCarXed = new Queue('Car Crossed'); 23 | 24 | walkToXing = new Uniform(60, 120); 25 | walkAcross = new Uniform(10, 20); 26 | walkAway = new Uniform(120, 180); 27 | 28 | driveToXing = new Uniform(5, 6); 29 | driveAway = new Uniform(10, 12); 30 | 31 | pedestrianArrivalInterval = new Exponential(60 / 10); // 10/min 32 | carArrivalInterval = new Exponential(60 / 6); // 6/min 33 | 34 | cycle = { 35 | red: 20, 36 | yellow: 10, 37 | green: 30, 38 | }; 39 | light = Signal.RED; 40 | 41 | // initialize Simulation 42 | constructor(options?: any) { 43 | super(options); 44 | this.timeUnit = 's'; 45 | this.qPedXing.grossPop.setHistogramParameters(3); 46 | this.qCarXing.grossPop.setHistogramParameters(2); 47 | if (this.timeEnd == null) { 48 | this.timeEnd = 3600 * 24; // 24 hours 49 | } 50 | } 51 | 52 | // create entity generators 53 | onStarting() { 54 | super.onStarting(); 55 | this.activate(new TrafficLight()); 56 | this.generateEntities(Pedestrian, this.pedestrianArrivalInterval); 57 | this.generateEntities(Car, this.carArrivalInterval); 58 | } 59 | } 60 | 61 | 62 | // pedestrians 63 | export class Pedestrian extends Entity { 64 | async script() { 65 | let sim = this.simulation; 66 | 67 | // walk to crosswalk 68 | await this.delay(sim.walkToXing.sample(), { 69 | queues: [sim.qPedArr, sim.qPedXing] 70 | }); 71 | 72 | // enter pedestrian crosswalk 73 | await this.enterQueue(sim.qPedXing); 74 | 75 | // wait for green light 76 | while (sim.light != Signal.GREEN) { 77 | await this.waitSignal(Signal.GREEN); 78 | } 79 | 80 | // leave crossing 81 | this.leaveQueue(sim.qPedXing); 82 | 83 | // walk across and away 84 | await this.delay(sim.walkAcross.sample(), { 85 | queues: [sim.qPedXing, sim.qPedXed] 86 | }); 87 | await this.delay(sim.walkAway.sample(), { 88 | queues: [sim.qPedXed, sim.qPedLeave] 89 | }); 90 | } 91 | } 92 | 93 | // cars 94 | export class Car extends Entity { 95 | async script() { 96 | let sim = this.simulation; 97 | 98 | // drive to crosswalk 99 | await this.delay(sim.driveToXing.sample(), { 100 | queues: [sim.qCarArr, sim.qCarXing] 101 | }); 102 | 103 | // enter crosswalk 104 | await this.enterQueue(sim.qCarXing); 105 | 106 | // wait until red for pedestrians 107 | while (sim.light != Signal.RED) { 108 | await this.waitSignal(Signal.RED); 109 | } 110 | 111 | // leave crosswalk 112 | this.leaveQueue(sim.qCarXing); 113 | 114 | // drive away 115 | await this.delay(sim.driveAway.sample(), { 116 | queues: [sim.qCarXing, sim.qCarXed] 117 | }); 118 | } 119 | } 120 | 121 | // traffic light 122 | class TrafficLight extends Entity { 123 | async script() { 124 | let sim = this.simulation; 125 | while (true) { 126 | 127 | // turn green to allow pedestrians to cross 128 | this.setLight(Signal.GREEN); 129 | await this.delay(sim.cycle.green); 130 | 131 | // turn yellow to clear pedestrians 132 | this.setLight(Signal.YELLOW); 133 | await this.delay(sim.cycle.yellow); 134 | 135 | // turn red to allow cars to cross 136 | this.setLight(Signal.RED); 137 | await this.delay(sim.cycle.red); 138 | } 139 | } 140 | setLight(value: Signal) { 141 | this.simulation.light = value; 142 | this.sendSignal(value); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /simulations/randomvartest.ts: -------------------------------------------------------------------------------- 1 | import { Simulation } from '../simscript/simulation'; 2 | import { Entity } from '../simscript/entity'; 3 | import { Tally } from '../simscript/tally'; 4 | import { 5 | RandomVar, Uniform, Triangular, Empirical, 6 | Exponential, Erlang, Gamma, 7 | Normal, LogNormal, RandomInt 8 | } from '../simscript/random'; 9 | 10 | // items in drop-down 11 | interface IRandomVar { 12 | var: RandomVar; 13 | name: string; 14 | binSize: number; 15 | } 16 | 17 | // RandomVarTest simulation 18 | export class RandomVarTest extends Simulation { 19 | _tally = new Tally(); 20 | _sampleSize = 1000; 21 | _seeded = false; 22 | _randomVars: IRandomVar[]; 23 | _index = 0; 24 | 25 | constructor(options?: any) { 26 | super(options); 27 | this.createRandomVars(); 28 | } 29 | 30 | get seeded() { 31 | return this._seeded; 32 | } 33 | set seeded(value: boolean) { 34 | this._seeded = value; 35 | this.createRandomVars(); 36 | } 37 | get sampleSize(): number { 38 | return this._sampleSize; 39 | } 40 | set sampleSize(value: number) { 41 | this._sampleSize = value; 42 | } 43 | get randomVars(): any { 44 | return this._randomVars; 45 | } 46 | get randomVar(): IRandomVar { 47 | return this._randomVars[this._index]; 48 | } 49 | get tally(): Tally { 50 | return this._tally; 51 | } 52 | get randomVarIndex(): number { 53 | return this._index; 54 | } 55 | set randomVarIndex(value: number) { 56 | if (value != this._index) { 57 | this._index = value; 58 | this.start(); // show the new random var 59 | } 60 | } 61 | createRandomVars() { 62 | const seed = this._seeded ? 1 : null; 63 | this._randomVars = [ 64 | { var: new RandomVar(seed), name: 'Uniform(0, 1)', binSize: 0.1 }, 65 | { var: new Uniform(5, 10, seed), name: 'Uniform(5, 10)', binSize: 0.5 }, 66 | { var: new Triangular(5, 6, 10, seed), name: 'Triangular(5, 6, 10)', binSize: 0.5 }, 67 | { var: new Empirical([5, 8, 10], [0, .8, 1], seed), name: 'Empirical([5, 8, 10], [0, .8, 1])', binSize: 0.5 }, 68 | { var: new Exponential(10, seed), name: 'Exponential(10)', binSize: 20 }, 69 | { var: new Normal(10, 2, true, seed), name: 'Normal(10, 2)', binSize: 2 }, 70 | { var: new LogNormal(10, 2, seed), name: 'LogNormal(10, 2)', binSize: 2 }, 71 | { var: new RandomInt(10, seed), name: 'RandomInt(10)', binSize: 1 }, 72 | { var: new EightEighty(seed), name: 'EightEighty()', binSize: 10 }, 73 | 74 | { var: new Erlang(1, 2, seed), name: 'Erlang(1, 2)', binSize: 1 }, 75 | { var: new Erlang(2, 2, seed), name: 'Erlang(2, 2)', binSize: 1 }, 76 | { var: new Erlang(3, 2, seed), name: 'Erlang(3, 2)', binSize: 1 }, 77 | { var: new Erlang(5, 1, seed), name: 'Erlang(5, 1)', binSize: 1 }, 78 | { var: new Erlang(7, 0.5, seed), name: 'Erlang(7, 0.5)', binSize: 1 }, 79 | { var: new Erlang(9, 1, seed), name: 'Erlang(9, 1)', binSize: 1 }, 80 | { var: new Erlang(1, 1, seed), name: 'Erlang(1, 1)', binSize: 1 }, 81 | 82 | { var: new Gamma(1, 2, seed), name: 'Gamma(1, 2)', binSize: 1 }, 83 | { var: new Gamma(2, 2, seed), name: 'Gamma(2, 2)', binSize: 1 }, 84 | { var: new Gamma(3, 2, seed), name: 'Gamma(3, 2)', binSize: 1 }, 85 | { var: new Gamma(5, 1, seed), name: 'Gamma(5, 1)', binSize: 1 }, 86 | { var: new Gamma(9, 0.5, seed), name: 'Gamma(9, 0.5)', binSize: 1 }, 87 | { var: new Gamma(7.5, 1, seed), name: 'Gamma(7.5, 1)', binSize: 1 }, 88 | { var: new Gamma(0.5, 1, seed), name: 'Gamma(0.5, 1)', binSize: 1 }, 89 | ]; 90 | } 91 | onStarting() { 92 | super.onStarting(); 93 | this.createRandomVars(); 94 | this.activate(new RandomVarTester()); 95 | } 96 | } 97 | 98 | // tester entity 99 | export class RandomVarTester extends Entity { 100 | async script() { 101 | const sim = this.simulation, 102 | rv = sim.randomVar, 103 | tally = sim.tally; 104 | tally.reset(); 105 | tally.setHistogramParameters(rv.binSize); 106 | for (let i = 0; i < sim.sampleSize; i++) { 107 | tally.add(rv.var.sample()); 108 | } 109 | } 110 | } 111 | 112 | // RandomVar that returns either 8 or 80 113 | export class EightEighty extends RandomVar { 114 | sample(): number { 115 | return super.sample() < 0.5 ? 8 : 80; 116 | } 117 | } -------------------------------------------------------------------------------- /simulations/car-follow.ts: -------------------------------------------------------------------------------- 1 | import { Simulation } from '../simscript/simulation'; 2 | import { Queue } from '../simscript/queue'; 3 | import { Entity, IAnimationPosition } from '../simscript/entity'; 4 | import { Uniform, Exponential } from '../simscript/random'; 5 | import { Point, IPoint } from '../simscript/util'; 6 | import { Event, EventArgs } from '../simscript/event'; 7 | 8 | interface ICar { 9 | speed: number; 10 | maxSpeed: number; 11 | accel: number; 12 | position: number; 13 | } 14 | 15 | export class CarFollow extends Simulation { 16 | timeIncrement = 2; // seconds 17 | totalCars = 1000; // number of cars to simulate 18 | stripLength = 1000; // meters 19 | carSpeeds = new Uniform(40 / 3.6, 100 / 3.6); // 40-100 km/h in m/s 20 | interArrival = new Exponential(20); // avg seconds between car arrivals 21 | qStrip = new Queue('strip'); 22 | readonly carFinished = new Event(); 23 | 24 | onStarting(e) { 25 | super.onStarting(e); 26 | this.maxTimeStep = this.timeIncrement; 27 | this.generateEntities(Car, this.interArrival, this.totalCars); 28 | } 29 | onCarFinished(e?: EventArgs) { 30 | this.carFinished.raise(this, e); 31 | } 32 | } 33 | 34 | export class Car extends Entity implements ICar { 35 | speed = 0; // starting speed 36 | accel = 10; // acceleration/deceleration 37 | position = 0; // current position 38 | maxSpeed = 0; // random value from simulation 39 | 40 | async script() { 41 | const sim = this.simulation; 42 | const dt = sim.timeIncrement; 43 | this.maxSpeed = sim.carSpeeds.sample(); 44 | 45 | // enter the strip 46 | this.enterQueueImmediately(sim.qStrip); 47 | 48 | // loop until the end of the strip 49 | while (this.position < sim.stripLength) { 50 | this.speed = this.getSpeed(dt); 51 | await this.delay(dt); 52 | this.position += this.speed * dt; 53 | } 54 | 55 | // exit the strip 56 | this.leaveQueue(sim.qStrip); 57 | sim.onCarFinished(); 58 | } 59 | 60 | // gets the car's animation position 61 | getAnimationPosition(q: Queue, start: IPoint, end: IPoint): IAnimationPosition { 62 | const 63 | sim = this.simulation, 64 | pt = Point.interpolate(start, end, this.position / sim.stripLength); 65 | return { 66 | position: pt, 67 | angle: Point.angle(start, end, false) 68 | } 69 | } 70 | 71 | // gets the vehicle speed taking into account the max safe speed 72 | getSpeed(dt: number): number { 73 | const safeSpeed = Math.min(this.getSafeSpeed(dt), this.maxSpeed); 74 | if (safeSpeed > this.speed) { // accelerate 75 | return Math.min(safeSpeed, this.speed + this.accel * dt); 76 | } 77 | if (safeSpeed < this.speed) { // decelerate 78 | return Math.max(safeSpeed, this.speed - this.accel * dt); 79 | } 80 | return this.speed; // no change 81 | } 82 | 83 | // gets the speed that would allow this vehicle to stop 84 | // before hitting the vehicle ahead if it were to stop. 85 | getSafeSpeed(dt: number): number { 86 | 87 | // assume max speed 88 | let speed = this.maxSpeed; 89 | 90 | // get vehicle ahead of us (or end of the road) 91 | const vAhead = this.getCarAhead(); 92 | if (vAhead != null) { 93 | 94 | // calculate vehicle ahead's breaking distance 95 | const dAhead = vAhead.position - this.position; 96 | let breakingDistance = dAhead; 97 | if (vAhead.speed && vAhead.accel) { 98 | breakingDistance += (vAhead.speed * vAhead.speed) / (2 * vAhead.accel); 99 | } 100 | 101 | // calculate max speed that allows us to break 102 | const rad = dt * dt / 4 - (this.speed * dt - 2 * breakingDistance) / this.accel; 103 | speed = rad > 0 104 | ? +this.accel * (Math.sqrt(rad) - dt / 2) 105 | : -this.accel * dt / 2; // no time to stop, negative speed... 106 | } 107 | 108 | // done 109 | return Math.max(0, speed); 110 | } 111 | 112 | // gets the car that is ahead of this one 113 | getCarAhead(): ICar { 114 | const 115 | sim = this.simulation, 116 | strip = sim.qStrip.entities; 117 | 118 | // index 0 is the first car 119 | let index = strip.indexOf(this); 120 | if (index > 0) { 121 | return strip[index - 1] as Car; 122 | } 123 | 124 | // no vehicle ahead, stop at the end of the strip 125 | return { 126 | speed: 0, 127 | maxSpeed: 0, 128 | accel: 0, 129 | position: sim.stripLength 130 | }; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Helvetica, Arial, sans-serif; 3 | -webkit-font-smoothing: antialiased; 4 | -moz-osx-font-smoothing: grayscale; 5 | color: #2c3e50; 6 | max-width: 75ch; 7 | margin: auto; 8 | } 9 | 10 | h1, 11 | h2 { 12 | margin-top: 1.75em; 13 | line-height: 2em; 14 | } 15 | 16 | .sample-group h1, 17 | h2 { 18 | border-top: 1px solid rgba(0, 0, 0, .2); 19 | } 20 | 21 | code { 22 | font-weight: bold; 23 | font-size: 120%; 24 | background: #f0f0f0; 25 | } 26 | 27 | button, 28 | input, 29 | select { 30 | padding: .5em .3em; 31 | margin: .5em 0 1em 0; 32 | border-radius: 0; 33 | font-family: inherit; 34 | font-size: inherit; 35 | } 36 | 37 | button { 38 | padding: .5em 1em; 39 | } 40 | 41 | button:hover { 42 | background: rgba(0, 0, 0, .2); 43 | } 44 | 45 | input[type=range] { 46 | padding: 0; 47 | } 48 | 49 | h1 button, 50 | h2 button { 51 | padding: 0; 52 | width: 1.5em; 53 | height: 1.5em; 54 | border-radius: .3em; 55 | margin-right: .25em; 56 | margin-bottom: 0; 57 | } 58 | 59 | .error { 60 | color: darkred; 61 | font-weight: bold; 62 | } 63 | 64 | button.hide { 65 | display: none; 66 | margin-left: 5em; 67 | } 68 | 69 | label { 70 | display: flex; 71 | align-items: center; 72 | margin-left: 1em; 73 | gap: .5em; 74 | min-height: 1.75em; 75 | } 76 | 77 | /* crosswalk traffic light cycle parameters */ 78 | span.light { 79 | display: inline-block; 80 | width: 1em; 81 | height: 1em; 82 | border-radius: 50%; 83 | border: 4px solid rgba(0, 0, 0, .5); 84 | vertical-align: middle; 85 | margin-right: .1em; 86 | } 87 | 88 | span.light.red { 89 | background: red; 90 | } 91 | 92 | span.light.yellow { 93 | background: orange; 94 | } 95 | 96 | span.light.green { 97 | background: green; 98 | } 99 | 100 | /* animation */ 101 | div.ss-anim { 102 | position: relative; 103 | height: 300px; 104 | } 105 | 106 | div.ss-anim>div { 107 | position: absolute; 108 | } 109 | 110 | div.ss-anim .ss-queue { 111 | width: 1em; 112 | height: 1em; 113 | border-radius: 4px; 114 | transform: translate(-50%, -50%); 115 | background: orange; 116 | opacity: 0.03; 117 | } 118 | 119 | div.ss-anim .light { 120 | top: 5%; 121 | left: 50%; 122 | transform: translate(-50%, 0); 123 | background: rgba(0, 0, 0, 0.1); 124 | border: 2px solid rgba(0, 0, 0, .2); 125 | border-radius: 1em; 126 | padding: .1em; 127 | } 128 | 129 | div.ss-anim .light div { 130 | width: 1em; 131 | height: 1em; 132 | margin: .1em; 133 | border: 2px solid rgba(0, 0, 0, .5); 134 | border-radius: 1em; 135 | opacity: .2; 136 | } 137 | 138 | div.ss-anim .light .red { 139 | background: red; 140 | } 141 | 142 | div.ss-anim .light .yellow { 143 | background: yellow; 144 | } 145 | 146 | div.ss-anim .light .green { 147 | background: green; 148 | } 149 | 150 | div.ss-anim .street { 151 | width: 80%; 152 | height: 20%; 153 | top: 50%; 154 | left: 10%; 155 | background: rgba(0, 0, 0, 0.1); 156 | transform: perspective(400px) rotateX(30deg); 157 | } 158 | 159 | div.ss-anim .crosswalk { 160 | width: 10%; 161 | height: 20%; 162 | top: 50%; 163 | left: 45%; 164 | background-image: repeating-linear-gradient(45deg, 165 | black, 166 | black 10px, 167 | white 10px, 168 | white 20px); 169 | transform: perspective(400px) rotateX(30deg); 170 | } 171 | 172 | div.ss-anim .car-arr { 173 | top: 60%; 174 | left: 10%; 175 | } 176 | 177 | div.ss-anim .car-xing { 178 | top: 60%; 179 | left: 40%; 180 | } 181 | 182 | div.ss-anim .car-xed { 183 | top: 60%; 184 | left: 90%; 185 | } 186 | 187 | div.ss-anim .ped-arr { 188 | top: 85%; 189 | left: 10%; 190 | } 191 | 192 | div.ss-anim .ped-xing { 193 | top: 75%; 194 | left: 50%; 195 | } 196 | 197 | div.ss-anim .ped-xed { 198 | top: 45%; 199 | left: 50%; 200 | } 201 | 202 | div.ss-anim .ped-leave { 203 | top: 35%; 204 | left: 90%; 205 | } 206 | 207 | .ss-anim .ss-entity { 208 | position: absolute; 209 | } 210 | 211 | div.ss-anim .ss-entity.ped { 212 | opacity: 0.8; 213 | } 214 | 215 | div.ss-anim .time-now { 216 | top: 1em; 217 | right: 10%; 218 | } 219 | 220 | div.ss-anim .time-now span { 221 | font-weight: bold; 222 | } 223 | 224 | /* SVG animation*/ 225 | svg.ss-anim { 226 | width: 100%; 227 | height: 300px; 228 | /*overflow: visible;*/ 229 | } 230 | 231 | svg.ss-anim g.light { 232 | fill: rgba(0, 0, 0, 0.1); 233 | stroke: rgba(0, 0, 0, 0.3); 234 | stroke-width: 2px; 235 | } 236 | 237 | svg.ss-anim g.light circle { 238 | opacity: 0.2; 239 | } 240 | 241 | svg.ss-anim g.light circle.red { 242 | fill: red; 243 | } 244 | 245 | svg.ss-anim g.light circle.yellow { 246 | fill: yellow; 247 | } 248 | 249 | svg.ss-anim g.light circle.green { 250 | fill: green; 251 | } 252 | 253 | svg.ss-anim .ss-queue { 254 | fill: orange; 255 | opacity: 0.3; 256 | } 257 | 258 | svg.ss-anim .street { 259 | fill: rgba(0, 0, 0, 0.1); 260 | } 261 | 262 | svg.ss-anim .crosswalk { 263 | fill: rgba(0, 0, 0, 0.3); 264 | } 265 | 266 | .ss-anim { 267 | width: 100%; 268 | height: 450px; 269 | border: 2px solid black; 270 | padding: 12px; 271 | } 272 | 273 | .ss-anim.asteroids { 274 | overflow: hidden; 275 | background: black; 276 | width: 80%; 277 | margin: auto auto; 278 | } 279 | 280 | table.params td, 281 | table.params th { 282 | padding-left: 2em; 283 | font-size: 90%; 284 | text-align: right; 285 | } 286 | 287 | table.params .gpss { 288 | color: darkgreen; 289 | } 290 | 291 | table.params .ss { 292 | color: darkblue; 293 | display: inline-block; 294 | margin-bottom: 0.5em; 295 | } 296 | 297 | .sample-group .body { 298 | margin-left: 1em; 299 | } 300 | 301 | /* style the steering animation elements */ 302 | svg.ss-anim.steering { 303 | display: block; 304 | max-height: 60vh; 305 | margin: 1em auto; 306 | border: 4px solid lightgrey; 307 | border-radius: 0.5em; 308 | overflow: hidden; 309 | } 310 | -------------------------------------------------------------------------------- /simscript/util.ts: -------------------------------------------------------------------------------- 1 | import { Event } from './event'; 2 | 3 | /** 4 | * Throws an Exception if a condition is false. 5 | * 6 | * @param condition Boolean value representing the condition to test. 7 | * @param msg Message of the Exception thrown if the condition is false. 8 | * This parameter can be a string or a function that returns a string. 9 | */ 10 | export function assert(condition: boolean, msg: string | Function) { 11 | if (!condition) { 12 | if (typeof msg === 'function') { 13 | msg = msg(); 14 | } 15 | console.error(msg); 16 | throw msg; 17 | } 18 | } 19 | 20 | /** 21 | * Formats a number using the current culture. 22 | * 23 | * @param value Number to be formatted. 24 | * @param decimals Number of decimals to display. 25 | * @returns A string containing the representation of the given number. 26 | */ 27 | export function format(value: number, decimals = 2) { 28 | return _getNumberFormat(decimals).format(value); 29 | } 30 | const _numberFormats: any = {}; 31 | function _getNumberFormat(decimals: number): Intl.NumberFormat { 32 | let nf = _numberFormats[decimals]; 33 | if (!nf) { 34 | nf = _numberFormats[decimals] = new Intl.NumberFormat(navigator.language, { 35 | useGrouping: true, 36 | minimumFractionDigits: decimals, 37 | maximumFractionDigits: decimals 38 | }); 39 | } 40 | return nf; 41 | } 42 | 43 | /** 44 | * Binds an input element to a variable (parameter). 45 | * 46 | * @param id Id of the input element to bind. 47 | * @param initialValue Initial value applied to the input element. 48 | * @param onInput Function called when the input value changes. 49 | * @param suffix String appended to the span element after input range elements. 50 | * @param decimals Number of decimal places to show for numeric values. 51 | */ 52 | export function bind(id: string, initialValue: any, onInput: Function, suffix = '', decimals?: number) { 53 | const input = document.getElementById(id) as any; 54 | const isCheck = input.type == 'checkbox'; 55 | const isNumber = input.type == 'range' || input.type == 'number'; 56 | const isSelect = input instanceof HTMLSelectElement; 57 | 58 | // format value for display 59 | const fmt = (value: number) => { 60 | const dec = decimals != null 61 | ? decimals 62 | : value == Math.round(value) ? 0 : 2; 63 | return ` ${format(value, dec)}${suffix}`; 64 | } 65 | 66 | // set initial value 67 | if (isCheck) { 68 | input.checked = initialValue as boolean; 69 | } else if (isSelect) { 70 | input.selectedIndex = initialValue; 71 | } else if (isNumber) { 72 | input.valueAsNumber = initialValue; 73 | } else { 74 | input.value = initialValue; 75 | } 76 | 77 | // show current range value 78 | const span = input.type == 'range' 79 | ? input.insertAdjacentElement('afterend', document.createElement('span')) 80 | : null; 81 | if (span) { 82 | span.textContent = fmt(input.valueAsNumber); 83 | } 84 | 85 | // apply changes 86 | input.addEventListener('input', e => { 87 | if (span) { 88 | span.textContent = fmt(input.valueAsNumber); 89 | } 90 | const value = isCheck ? input.checked : 91 | isSelect ? input.selectedIndex : 92 | isNumber ? input.valueAsNumber : 93 | input.value; 94 | onInput(value); 95 | }); 96 | } 97 | 98 | /** 99 | * Applies a group of property values and event handlers to an object. 100 | * 101 | * @param obj Object that contains the properties and events. 102 | * @param options Object that contains the property values and event handlers to apply. 103 | */ 104 | export function setOptions(obj: any, options: any) { 105 | if (options) { 106 | for (let key in options) { 107 | assert(key in obj, `Property ${key} is not defined`); 108 | if (obj[key] instanceof Event) { // add event handler 109 | (obj[key] as Event).addEventListener(options[key]); 110 | } else { 111 | obj[key] = options[key]; // property assignment 112 | } 113 | } 114 | } 115 | } 116 | 117 | /** 118 | * Gets an HTML element from a query selector. 119 | * 120 | * @param selector An HTML element or a query selector string, or a jQuery object. 121 | */ 122 | export function getElement(selector: any): Element { 123 | let e = typeof selector === 'string' 124 | ? document.querySelector(selector) 125 | : selector; 126 | assert(e instanceof Element, 'Element not found:' + selector); 127 | return e; 128 | } 129 | 130 | /** 131 | * Clamps a value to a given range. 132 | * 133 | * @param value Value to clamp. 134 | * @param min Minimum allowed value, or null of there is no lower bound. 135 | * @param max Maximum allowed value, or null of there is no upper bound. 136 | * @returns The clamped value (value >= min && value <= max). 137 | */ 138 | export function clamp(value: number, min: number | null, max: number | null): number { 139 | return (min != null && value < min) ? min 140 | : (max != null && value > max) ? max 141 | : value; 142 | } 143 | 144 | /** 145 | * Checks whether a value is a number. 146 | * @param val Value to check. 147 | * @returns True if the value is a number. 148 | */ 149 | export function isNumber(val: any): boolean { 150 | return typeof val == 'number'; 151 | } 152 | 153 | /** 154 | * Defines the properties of point objects. 155 | */ 156 | export interface IPoint { 157 | x: number; 158 | y: number; 159 | z?: number; 160 | } 161 | 162 | /** 163 | * Represents a point with x, y, and z coordinates. 164 | */ 165 | export class Point implements IPoint { 166 | x: number; 167 | y: number; 168 | z: number; 169 | 170 | /** 171 | * Instantiates a new instance of a {@link Point} object. 172 | * @param x X coordinate of the point. 173 | * @param y Y coordinate of the point. 174 | * @param z Z coordinate of the point. 175 | */ 176 | constructor(x = 0, y = 0, z = 0) { 177 | this.x = x; 178 | this.y = y; 179 | this.z = z; 180 | } 181 | /** 182 | * Creates a clone of a given {@link IPoint} object. 183 | * @param p {@link IPoint} object to clone. 184 | * @returns A copy of the given {@link IPoint} object. 185 | */ 186 | static clone(p: IPoint): IPoint { 187 | return { 188 | x: p.x, 189 | y: p.y, 190 | z: p.z 191 | } 192 | } 193 | /** 194 | * Copies an {@link IPoint} object into another. 195 | * @param dst Destination {@link IPoint} object. 196 | * @param src Source {@link IPoint} object. 197 | * @returns The destination {@link IPoint} object. 198 | */ 199 | static copy(dst: IPoint, src: IPoint): IPoint { 200 | dst.x = src.x; 201 | dst.y = src.y; 202 | dst.z = src.z; 203 | return dst; 204 | } 205 | /** 206 | * Calculates the distance between two {@link IPoint} objects. 207 | * @param p1 First point. 208 | * @param p2 Second point. 209 | * @returns The distance between the two points. 210 | */ 211 | static distance(p1: IPoint, p2: IPoint): number { 212 | const dx = p1.x - p2.x; 213 | const dy = p1.y - p2.y; 214 | const dz = p1.z || 0 - p2.z || 0; 215 | return Math.sqrt(dx * dx + dy * dy + dz * dz); 216 | } 217 | /** 218 | * Calculates an {@link IPoint} by performing a linear interpolation 219 | * between two {@link IPoint} objects. 220 | * @param p1 First point. 221 | * @param p2 Second point. 222 | * @param t Coefficient that corresponds to the relative distance of 223 | * the result to the first point. Zero corresponds to the first point, 224 | * one to the second point. 225 | * @returns A point between the two given points. 226 | */ 227 | static interpolate(p1: IPoint, p2: IPoint, t: number): IPoint { 228 | return { 229 | x: p1.x + (p2.x - p1.x) * t, 230 | y: p1.y + (p2.y - p1.y) * t, 231 | z: (p1.z || 0) + (p2.z || 0 - p1.z || 0) * t 232 | }; 233 | } 234 | /** 235 | * Calculates the angle (in degrees or radians) of the line that 236 | * connects two points. 237 | * @param p1 First point. 238 | * @param p2 Second point. 239 | * @param radians Whether to return the result in radians. 240 | * @returns The angle (in degrees or radians) of the line that 241 | * connects the two points. 242 | */ 243 | static angle(p1: IPoint, p2: IPoint, radians = false): number { 244 | const angle = Math.atan2(p2.y - p1.y, p2.x - p1.x); 245 | return radians? angle : Math.round(angle * 180 / Math.PI); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /simscript/tally.ts: -------------------------------------------------------------------------------- 1 | import { setOptions, assert, clamp, format } from './util'; 2 | 3 | /** 4 | * Represents an entry in a histogram created by the 5 | * {@link Tally.getHistogram} method. 6 | */ 7 | export interface IHistogramEntry { 8 | from: number, 9 | to: number, 10 | count: number 11 | } 12 | 13 | /** 14 | * Specifies parameters used when creating histograms with the 15 | * {@link Tally.getHistogram} method. 16 | */ 17 | interface IHistogramParameters { 18 | size: number, 19 | min: number | null, 20 | max: number | null 21 | } 22 | 23 | /** 24 | * Class that collects observations and provides summary statistics 25 | * and histograms. 26 | * 27 | * The {@link Tally} class is used by {@link Queue} objects to provide 28 | * statistics about queue populations and dwell times. 29 | */ 30 | export class Tally { 31 | private _cnt = 0; 32 | private _min = 0; 33 | private _max = 0; 34 | private _sum = 0; 35 | private _sum2 = 0; 36 | private _histo: Map | null = null; 37 | private _histoParms: IHistogramParameters | null = null; 38 | 39 | /** 40 | * Initializes a new instance of the {@link Tally} class. 41 | * 42 | * @param options Optional object uses to initialize the 43 | * {@link Tally} properties. 44 | */ 45 | constructor(options?: any) { 46 | setOptions(this, options); 47 | } 48 | 49 | /** 50 | * Gets the minimum observed value. 51 | */ 52 | get min(): number { 53 | return this._min; 54 | } 55 | /** 56 | * Gets the maximum observed value. 57 | */ 58 | get max(): number { 59 | return this._max; 60 | } 61 | /** 62 | * Gets the number of observed values. 63 | */ 64 | get cnt(): number { 65 | return this._cnt; 66 | } 67 | /** 68 | * Gets the average of the observed values. 69 | */ 70 | get avg(): number { 71 | return this._cnt > 0 72 | ? this._sum / this._cnt 73 | : 0; 74 | } 75 | /** 76 | * Gets the variance of the observed values. 77 | */ 78 | get var(): number { 79 | return this._cnt > 0 && this._max > this._min 80 | ? Math.max(0, (this._sum2 - this._sum * this._sum / this._cnt) / this._cnt) 81 | : 0; 82 | } 83 | /** 84 | * Gets the standard deviation of the observed values. 85 | */ 86 | get stdev(): number { 87 | return Math.sqrt(this.var); 88 | } 89 | /** 90 | * Adds a value to the {@link Tally}. 91 | * 92 | * @param value Value to add to the tally. 93 | * @param weight Weight of the observed value. 94 | */ 95 | add(value: number, weight = 1) { 96 | assert(weight >= 0, 'tally weights must be >= 0'); 97 | 98 | // keep track of min/max 99 | if (!this._cnt || value > this._max) this._max = value; 100 | if (!this._cnt || value < this._min) this._min = value; 101 | 102 | // keep counts 103 | this._cnt += weight; 104 | this._sum += value * weight; 105 | this._sum2 += value * value * weight; 106 | 107 | // update histogram 108 | if (this._histo) { 109 | value = clamp(value, this._histoParms.min, this._histoParms.max); 110 | let bin = Math.floor(value / this._histoParms.size); 111 | let cnt = this._histo.get(bin) || 0; 112 | this._histo.set(bin, cnt + weight); 113 | } 114 | } 115 | /** 116 | * Gets an array of {@link IHistogramEntry} objects that show 117 | * the {@link Tally} observations as a histogram. 118 | * 119 | * Before using this method, call the {@link setHistogramParameters} 120 | * to specify the desired histogram's bin size and limits. 121 | * 122 | * @returns An array of {@link IHistogramEntry} objects. 123 | */ 124 | getHistogram(): IHistogramEntry[] | null { 125 | if (this._histo) { 126 | 127 | // get sorted list of bins 128 | const 129 | bins = this._histo, 130 | keys = Array.from(bins.keys()); 131 | keys.sort((a, b) => a - b); // sort bins in ascending order 132 | 133 | // add missing keys 134 | for (let i = 1; i < keys.length; i++) { 135 | if (keys[i] > keys[i - 1] + 1) { 136 | keys.splice(i, 0, keys[i - 1] + 1); 137 | } 138 | } 139 | 140 | // build histogram 141 | const binSize = this._histoParms.size; 142 | let h = keys.map(key => { 143 | return { 144 | from: key * binSize, 145 | to: (key + 1) * binSize, 146 | count: bins.get(key) || 0 147 | } 148 | }); 149 | 150 | // honor min/max histogram parameters 151 | if (h.length) { 152 | const 153 | parms = this._histoParms, 154 | min = parms.min, 155 | max = parms.max; 156 | if (min != null && h[0].from > this.min) { 157 | h[0].from = this.min; 158 | } 159 | if (max != null && h[h.length - 1].to < this.max) { 160 | h[h.length - 1].to = this.max; 161 | } 162 | } 163 | 164 | // done 165 | return h; 166 | } 167 | return null; 168 | } 169 | /** 170 | * Gets an HTML string showing the {@link Tally} as a histogram. 171 | * 172 | * Before using this method, call the {@link setHistogramParameters} 173 | * to specify the desired histogram's bin size and limits. 174 | * 175 | * @param title The title for the histogram. 176 | * @param scale Factor applied to scale all values. For example, 177 | * if the simulation uses seconds and you want to show the values 178 | * in minutes, set **scale** to 1/60. 179 | * @returns An HTML string showing the {@link Tally} as a histogram. 180 | */ 181 | getHistogramChart(title = '', scale = 1): string { 182 | 183 | // get the histogram 184 | const histo = this.getHistogram(); 185 | 186 | // sanity 187 | if (!histo || !histo.length) { 188 | return ''; 189 | } 190 | 191 | // get parameters 192 | let maxCnt = 0; 193 | histo.forEach(e => maxCnt = Math.max(maxCnt, e.count)); 194 | const 195 | barWidth = Math.round(1 / histo.length * 100), 196 | dec = this._histoParms.size < 1 ? 1 : 0; 197 | 198 | // build bars 199 | let bars = ''; 200 | histo.forEach((e, index) => { 201 | const 202 | cls = this.avg >= e.from && this.avg <= e.to ? ' class="avg"' : '', 203 | hei = Math.round(e.count / maxCnt * 100), 204 | x = index * barWidth, 205 | gap = 5; 206 | bars += ` 207 | ${e.count} (${Math.round(e.count / this.cnt * 100)}%) 208 | 214 | 220 | ${format(e.from * scale, dec)}-${format(e.to * scale, dec)} 221 | 222 | `; 223 | }); 224 | return ` 225 |
226 |
${title}
227 | 228 | ${bars} 229 | 230 |
`; 231 | } 232 | /** 233 | * Sets the parameters used to build histograms for this {@link Tally}. 234 | * 235 | * Use the {@link getHistogram} and {@link getHistogramTable} methods 236 | * to create tally histograms that can be added to reports. 237 | * 238 | * The default value for this property is null, which prevents the 239 | * creation of any histograms. 240 | */ 241 | setHistogramParameters(binSize: number | null, min: number | null = null, max: number | null = null) { 242 | if (!binSize) { 243 | this._histoParms = null; 244 | this._histo = null; 245 | } else { 246 | assert(binSize > 0, 'bin size must be positive'); 247 | assert(min == null || max == null || min <= max, 'histogram min must be <= max'); 248 | this._histoParms = { 249 | size: binSize, 250 | min: min, 251 | max: max 252 | }; 253 | this._histo = new Map(); 254 | } 255 | } 256 | /** 257 | * Resets the {@link Tally} by clearing all values, statistics, and 258 | * histogram data. 259 | */ 260 | reset() { 261 | this._cnt = this._max = this._min = this._sum = this._sum2 = 0; 262 | if (this._histo) { 263 | this._histo.clear(); 264 | } 265 | } 266 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SimScript 2 | 3 | A **Discrete Event Simulation** Library in TypeScript with 4 | support for 2D and 3D animations. 5 | 6 | The [SimScript API Documentation](https://bernardo-castilho.github.io/simscript/docs/) 7 | describes all the classes in the **SimScript** library and their properties. 8 | 9 | This [Sample](https://bernardo-castilho.github.io/simscript/dist/index.html) 10 | is written using pure TypeScript (no frameworks). 11 | It shows several simulations, including 2D and 3D animations. 12 | The source code is available on 13 | [GitHub](https://github.com/Bernardo-Castilho/simscript). 14 | 15 | This [React Sample](https://bernardo-castilho.github.io/simscript/react/) 16 | shows how you can create React components to show simulations, 17 | with support for routing, custom parameter binding, and animations. 18 | The source code is available on 19 | [GitHub](https://github.com/Bernardo-Castilho/simscript-react). 20 | 21 | **SimScript** uses JavaScript's 22 | [async/await](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await) 23 | features to make simulation code easy to write and understand. 24 | 25 | **SimScript** simulations are built using these classes: 26 | 27 | ## Simulation Class 28 | 29 | Simulations create resources (queues) and entities which execute an async 30 | **script** method that describes the actions each entity should perform. 31 | 32 | The **Simulation** class is abstract. 33 | In most cases, you will create classes that extend it to create the 34 | queues and entities you need. 35 | 36 | ## Entity Class 37 | 38 | Entities represent active elements that execute scripts. Scripts are async 39 | methods that contain instructions for entities. 40 | Typical actions include entering and leaving **queues**, going through 41 | **delays**, and sending or waiting for **signals**. 42 | 43 | The **Entity** class is abstract. In most cases, you will create one 44 | or more classes that extend it to perform the actions required by your 45 | simulations. 46 | 47 | ## Queue Class 48 | 49 | Queues represent resources that can be seized and released by entities. 50 | Queues keep track of their utilization and may constrain the flow of 51 | entities through the simulation. 52 | 53 | ## Animation Class 54 | 55 | The **Animation** class connects a **Simulation** object to a host 56 | element that shows the simulation graphically, rendering entities 57 | waiting in queues or in transit between queues. 58 | 59 | Animations may be 2D (hosted in regular HTML DIV or SVG elements) 60 | or they may be 3D (hosted in [X3DOM](https://www.x3dom.org/) or 61 | [A-Frame](https://aframe.io) elements). 62 | 63 | Animations are useful for presentations and also for checking and 64 | debugging simulations. 65 | 66 | ## Network Class 67 | 68 | Networks are defined by sets of nodes and links. 69 | 70 | The **Network** class provides a **shortestPath** method that returns 71 | a list of links so entities may travel along the network. 72 | 73 | The **Network** class has a **getLinkDistance** that returns the 74 | distance represented by a link. 75 | You may create classes that extend **Network** and override this 76 | method to provide custom behaviors such as congestion and turning 77 | costs. For example: 78 | 79 | ```typescript 80 | // network with congestion cost 81 | class CongestionNetwork extends Network { 82 | getLinkDistance(link: ILink, prevLink?: ILink): number { 83 | let dist = super.getLinkDistance(link, prevLink); // get regular distance 84 | dist += dist * link.queue.pop * 0.5; // add congestion cost 85 | // optionally add turning cost based on link and prevLink... 86 | return dist; 87 | } 88 | } 89 | ``` 90 | 91 | ## Styles 92 | 93 | **SimScript** includes a CSS file with some simple formatting for the 94 | tables and histograms you can create with the **Simulation.getStatsTable** 95 | and **Tally.getHistogram** methods. 96 | 97 | To include that CSS in your projects, add this line to the main **ts** 98 | file in your project: 99 | 100 | ```typescript 101 | import "simscript/dist/simscript.css"; 102 | ``` 103 | 104 | # Example 105 | 106 | This is the classic Barbershop simulation written in **SimScript**: 107 | 108 | ```typescript 109 | // https://try-mts.com/gpss-introduction-and-barber-shop-simulation/ 110 | export class BarberShop extends Simulation { 111 | qJoe = new Queue('Joe', 1); 112 | qWait = new Queue('Wait Area'); 113 | 114 | // generate entities with inter-arrival times of 18 min for 8 hours * 7 days 115 | onStarting() { 116 | super.onStarting(); 117 | this.timeEnd = 60 * 8 * 7; // simulation times are in minutes 118 | this.generateEntities(Customer, new Uniform(18 - 6, 18 + 6)); 119 | } 120 | } 121 | class Customer extends Entity { 122 | service = new Uniform(15 - 3, 15 + 3); 123 | async script() { 124 | const shop = this.simulation; 125 | 126 | await this.enterQueue(shop.qWait); // enter the line 127 | await this.enterQueue(shop.qJoe); // seize Joe the barber 128 | this.leaveQueue(shop.qWait); // leave the line 129 | await this.delay(this.service.sample()); // get a haircut 130 | this.leaveQueue(shop.qJoe); // free Joe 131 | 132 | // or do all this with a single call to seize: 133 | // await this.seize(shop.qJoe, this.service.sample(), shop.qWait); 134 | } 135 | } 136 | ``` 137 | 138 | # Samples 139 | 140 | The links below show some samples of **SimScript** simulations: 141 | 142 | ## New Samples 143 | 144 | - [React Sample](https://bernardo-castilho.github.io/simscript/react/)\ 145 | Shows how you can use SimScript in React. 146 | The source code for this sample is available on 147 | [GitHub](https://github.com/Bernardo-Castilho/simscript-react). 148 | 149 | - [Steering Behaviors](https://stackblitz.com/edit/typescript-fhrhfm)\ 150 | Shows samples inspired by the article 151 | [Steering Behaviors For Autonomous Characters](http://www.red3d.com/cwr/steer/). 152 | The samples show you can implement entities that navigate around their 153 | world in a life-like, improvisational, and composable manner. 154 | 155 | - [GPSS-inspired samples](https://stackblitz.com/edit/typescript-mapmna)\ 156 | Shows several samples inspired by traditional 157 | [GPSS samples](http://www.minutemansoftware.com/tutorial/tutorial_manual.htm) 158 | published by Minuteman software. 159 | The samples show how you can use SimScript to simulate a wide range of practical applications 160 | and allow you to compare results obtained by GPSS and SimScript. 161 | 162 | - [Asteroids (SVG animation)](https://stackblitz.com/edit/typescript-mcoqyz)\ 163 | Shows how you can use Simscript to implement a simple arcade game with 164 | support for keyboard/touch events, sounds, and collision detection. 165 | 166 | ## Other Samples 167 | 168 | - [Barbershop](https://stackblitz.com/edit/typescript-efht9t)\ 169 | Classic GPSS simulation example: 170 | customers arrive at a barbershop, wait until the barber is available, get serviced, and leave. 171 | 172 | - [M/M/C](https://stackblitz.com/edit/typescript-xbntrv)\ 173 | Classic M/M/C queueing system. Entities arrive, are served by one of C servers, and leave. 174 | 175 | - [RandomVar](https://stackblitz.com/edit/typescript-nwknjs)\ 176 | Demonstrates some of the random variables implemented in **SimScript**. 177 | 178 | - [Crosswalk](https://stackblitz.com/edit/typescript-nq3vvd)\ 179 | Uses the **waitSignal** and **sendSignal** methods to simulate a crosswalk. 180 | 181 | - [Animated Crosswalk (SVG animation)](https://stackblitz.com/edit/typescript-395kik)\ 182 | Uses the **Animation** class to show an SVG-based animated version of the **Crosswalk** simulation. 183 | 184 | - [Animated Crosswalk (X3DOM animation)](https://stackblitz.com/edit/typescript-ehhn4e)\ 185 | Uses the **Animation** class to show an X3DOM-based animated version of the **Crosswalk** simulation. 186 | 187 | - [Animation Options (SVG animation)](https://stackblitz.com/edit/typescript-3zcuw1)\ 188 | Uses an SVG-based 2D animation to show the effect of some 189 | **Animation** and **Simulation** properties. 190 | 191 | - [Animation Options (A-Frame animation)](https://stackblitz.com/edit/typescript-pmkehn)\ 192 | Uses an [A-Frame](https://aframe.io)-based 3D animation to show the effect of some 193 | **Animation** and **Simulation** properties. 194 | 195 | - [Animation Options (X3DOM animation)](https://stackblitz.com/edit/typescript-oncuqe)\ 196 | Uses an [X3DOM](https://www.x3dom.org/)-based 3D animation to show the effect of some 197 | **Animation** and **Simulation** properties. 198 | 199 | - [Network Intro (SVG animation)](https://stackblitz.com/edit/typescript-zfm9hz)\ 200 | Uses an SVG-based 2D animation to show how to use SimScript's **Network** class. 201 | 202 | - [Network Intro (X3DOM animation)](https://stackblitz.com/edit/typescript-hl7cya)\ 203 | Uses an [X3DOM](https://www.x3dom.org/)-based 3D animation to show how to use SimScript's 204 | **Network** class. 205 | 206 | - [Car-Following Network (X3DOM animation)](https://stackblitz.com/edit/typescript-5hfpwt)\ 207 | Shows how you can customize the behavior of a SimScript **Network** to use a 208 | car-following model and to account for network congestion. 209 | 210 | # Get Involved 211 | 212 | If you have any suggestions to improve **SimScript**, or wish to report any issues, 213 | feel free to contact me via [email](mailto:bernardo-castilho@hotmail.com). 214 | 215 | You can also use GitHub to [submit issues](https://github.com/Bernardo-Castilho/SimScript/issues). 216 | 217 | -------------------------------------------------------------------------------- /simscript/README.md: -------------------------------------------------------------------------------- 1 | # SimScript 2 | 3 | A **Discrete Event Simulation** Library in TypeScript with 4 | support for 2D and 3D animations. 5 | 6 | The [SimScript API Documentation](https://bernardo-castilho.github.io/simscript/docs/) 7 | describes all the classes in the **SimScript** library and their properties. 8 | 9 | This [Sample](https://bernardo-castilho.github.io/simscript/dist/index.html) 10 | is written using pure TypeScript (no frameworks). 11 | It shows several simulations, including 2D and 3D animations. 12 | The source code is available on 13 | [GitHub](https://github.com/Bernardo-Castilho/simscript). 14 | 15 | This [React Sample](https://bernardo-castilho.github.io/simscript/react/) 16 | shows how you can create React components to show simulations, 17 | with support for routing, custom parameter binding, and animations. 18 | The source code is available on 19 | [GitHub](https://github.com/Bernardo-Castilho/simscript-react). 20 | 21 | **SimScript** uses JavaScript's 22 | [async/await](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await) 23 | features to make simulation code easy to write and understand. 24 | 25 | **SimScript** simulations are built using these classes: 26 | 27 | ## Simulation Class 28 | 29 | Simulations create resources (queues) and entities which execute an async 30 | **script** method that describes the actions each entity should perform. 31 | 32 | The **Simulation** class is abstract. 33 | In most cases, you will create classes that extend it to create the 34 | queues and entities you need. 35 | 36 | ## Entity Class 37 | 38 | Entities represent active elements that execute scripts. Scripts are async 39 | methods that contain instructions for entities. 40 | Typical actions include entering and leaving **queues**, going through 41 | **delays**, and sending or waiting for **signals**. 42 | 43 | The **Entity** class is abstract. In most cases, you will create one 44 | or more classes that extend it to perform the actions required by your 45 | simulations. 46 | 47 | ## Queue Class 48 | 49 | Queues represent resources that can be seized and released by entities. 50 | Queues keep track of their utilization and may constrain the flow of 51 | entities through the simulation. 52 | 53 | ## Animation Class 54 | 55 | The **Animation** class connects a **Simulation** object to a host 56 | element that shows the simulation graphically, rendering entities 57 | waiting in queues or in transit between queues. 58 | 59 | Animations may be 2D (hosted in regular HTML DIV or SVG elements) 60 | or they may be 3D (hosted in [X3DOM](https://www.x3dom.org/) or 61 | [A-Frame](https://aframe.io) elements). 62 | 63 | Animations are useful for presentations and also for checking and 64 | debugging simulations. 65 | 66 | ## Network Class 67 | 68 | Networks are defined by sets of nodes and links. 69 | 70 | The **Network** class provides a **shortestPath** method that returns 71 | a list of links so entities may travel along the network. 72 | 73 | The **Network** class has a **getLinkDistance** that returns the 74 | distance represented by a link. 75 | You may create classes that extend **Network** and override this 76 | method to provide custom behaviors such as congestion and turning 77 | costs. For example: 78 | 79 | ```typescript 80 | // network with congestion cost 81 | class CongestionNetwork extends Network { 82 | getLinkDistance(link: ILink, prevLink?: ILink): number { 83 | let dist = super.getLinkDistance(link, prevLink); // get regular distance 84 | dist += dist * link.queue.pop * 0.5; // add congestion cost 85 | // optionally add turning cost based on link and prevLink... 86 | return dist; 87 | } 88 | } 89 | ``` 90 | 91 | ## Styles 92 | 93 | **SimScript** includes a CSS file with some simple formatting for the 94 | tables and histograms you can create with the **Simulation.getStatsTable** 95 | and **Tally.getHistogram** methods. 96 | 97 | To include that CSS in your projects, add this line to the main **ts** 98 | file in your project: 99 | 100 | ```typescript 101 | import "simscript/dist/simscript.css"; 102 | ``` 103 | 104 | # Example 105 | 106 | This is the classic Barbershop simulation written in **SimScript**: 107 | 108 | ```typescript 109 | // https://try-mts.com/gpss-introduction-and-barber-shop-simulation/ 110 | export class BarberShop extends Simulation { 111 | qJoe = new Queue('Joe', 1); 112 | qWait = new Queue('Wait Area'); 113 | 114 | // generate entities with inter-arrival times of 18 min for 8 hours * 7 days 115 | onStarting() { 116 | super.onStarting(); 117 | this.timeEnd = 60 * 8 * 7; // simulation times are in minutes 118 | this.generateEntities(Customer, new Uniform(18 - 6, 18 + 6)); 119 | } 120 | } 121 | class Customer extends Entity { 122 | service = new Uniform(15 - 3, 15 + 3); 123 | async script() { 124 | const shop = this.simulation; 125 | 126 | await this.enterQueue(shop.qWait); // enter the line 127 | await this.enterQueue(shop.qJoe); // seize Joe the barber 128 | this.leaveQueue(shop.qWait); // leave the line 129 | await this.delay(this.service.sample()); // get a haircut 130 | this.leaveQueue(shop.qJoe); // free Joe 131 | 132 | // or do all this with a single call to seize: 133 | // await this.seize(shop.qJoe, this.service.sample(), shop.qWait); 134 | } 135 | } 136 | ``` 137 | 138 | # Samples 139 | 140 | The links below show some samples of **SimScript** simulations: 141 | 142 | ## New Samples 143 | 144 | - [React Sample](https://bernardo-castilho.github.io/simscript/react/)\ 145 | Shows how you can use SimScript in React. 146 | The source code for this sample is available on 147 | [GitHub](https://github.com/Bernardo-Castilho/simscript-react). 148 | 149 | - [Steering Behaviors](https://stackblitz.com/edit/typescript-fhrhfm)\ 150 | Shows samples inspired by the article 151 | [Steering Behaviors For Autonomous Characters](http://www.red3d.com/cwr/steer/). 152 | The samples show you can implement entities that navigate around their 153 | world in a life-like, improvisational, and composable manner. 154 | 155 | - [GPSS-inspired samples](https://stackblitz.com/edit/typescript-mapmna)\ 156 | Shows several samples inspired by traditional 157 | [GPSS samples](http://www.minutemansoftware.com/tutorial/tutorial_manual.htm) 158 | published by Minuteman software. 159 | The samples show how you can use SimScript to simulate a wide range of practical applications 160 | and allow you to compare results obtained by GPSS and SimScript. 161 | 162 | - [Asteroids (SVG animation)](https://stackblitz.com/edit/typescript-mcoqyz)\ 163 | Shows how you can use Simscript to implement a simple arcade game with 164 | support for keyboard/touch events, sounds, and collision detection. 165 | 166 | ## Other Samples 167 | 168 | - [Barbershop](https://stackblitz.com/edit/typescript-efht9t)\ 169 | Classic GPSS simulation example: 170 | customers arrive at a barbershop, wait until the barber is available, get serviced, and leave. 171 | 172 | - [M/M/C](https://stackblitz.com/edit/typescript-xbntrv)\ 173 | Classic M/M/C queueing system. Entities arrive, are served by one of C servers, and leave. 174 | 175 | - [RandomVar](https://stackblitz.com/edit/typescript-nwknjs)\ 176 | Demonstrates some of the random variables implemented in **SimScript**. 177 | 178 | - [Crosswalk](https://stackblitz.com/edit/typescript-nq3vvd)\ 179 | Uses the **waitSignal** and **sendSignal** methods to simulate a crosswalk. 180 | 181 | - [Animated Crosswalk (SVG animation)](https://stackblitz.com/edit/typescript-395kik)\ 182 | Uses the **Animation** class to show an SVG-based animated version of the **Crosswalk** simulation. 183 | 184 | - [Animated Crosswalk (X3DOM animation)](https://stackblitz.com/edit/typescript-ehhn4e)\ 185 | Uses the **Animation** class to show an X3DOM-based animated version of the **Crosswalk** simulation. 186 | 187 | - [Animation Options (SVG animation)](https://stackblitz.com/edit/typescript-3zcuw1)\ 188 | Uses an SVG-based 2D animation to show the effect of some 189 | **Animation** and **Simulation** properties. 190 | 191 | - [Animation Options (A-Frame animation)](https://stackblitz.com/edit/typescript-pmkehn)\ 192 | Uses an [A-Frame](https://aframe.io)-based 3D animation to show the effect of some 193 | **Animation** and **Simulation** properties. 194 | 195 | - [Animation Options (X3DOM animation)](https://stackblitz.com/edit/typescript-oncuqe)\ 196 | Uses an [X3DOM](https://www.x3dom.org/)-based 3D animation to show the effect of some 197 | **Animation** and **Simulation** properties. 198 | 199 | - [Network Intro (SVG animation)](https://stackblitz.com/edit/typescript-zfm9hz)\ 200 | Uses an SVG-based 2D animation to show how to use SimScript's **Network** class. 201 | 202 | - [Network Intro (X3DOM animation)](https://stackblitz.com/edit/typescript-hl7cya)\ 203 | Uses an [X3DOM](https://www.x3dom.org/)-based 3D animation to show how to use SimScript's 204 | **Network** class. 205 | 206 | - [Car-Following Network (X3DOM animation)](https://stackblitz.com/edit/typescript-5hfpwt)\ 207 | Shows how you can customize the behavior of a SimScript **Network** to use a 208 | car-following model and to account for network congestion. 209 | 210 | # Get Involved 211 | 212 | If you have any suggestions to improve **SimScript**, or wish to report any issues, 213 | feel free to contact me via [email](mailto:bernardo-castilho@hotmail.com). 214 | 215 | You can also use GitHub to [submit issues](https://github.com/Bernardo-Castilho/SimScript/issues). 216 | 217 | -------------------------------------------------------------------------------- /simulations/network-steering.ts: -------------------------------------------------------------------------------- 1 | import { RandomInt, Uniform } from '../simscript/random'; 2 | import { ILink } from '../simscript/network'; 3 | import { setOptions, Point } from '../simscript/util'; 4 | 5 | import { createNetwork } from './network-intro'; 6 | import { SteeringBehaviors, SteeringVehicle, SeekBehavior, AvoidBehavior } from './steering'; 7 | 8 | const SPEED_MIN = 5; 9 | const SPEED_MAX = 100; 10 | const SEGMENT_COUNT = 10; 11 | 12 | // Simulation with a network and some Steering Vehicles 13 | export class NetworkSteering extends SteeringBehaviors { 14 | network = createNetwork(3, 5, 200, false); // nodes are 200m apart 15 | rndNode = new RandomInt(this.network.nodes.length - 1); 16 | speed = new Uniform(SPEED_MIN, SPEED_MAX); 17 | vehicles: NetworkSteeringVehicle[] = []; 18 | vehiclesDone = 0; 19 | 20 | onStarting() { 21 | super.onStarting(); 22 | 23 | this.maxTimeStep = 0.05; 24 | this.vehicles = []; 25 | this.vehiclesDone = 0; 26 | 27 | // testing 28 | if (false) { 29 | 30 | // head-on 31 | const 32 | network = this.network, 33 | nodes = network.nodes; 34 | this.vehicles.push(new NetworkSteeringVehicle(this, { 35 | speedMax: SPEED_MAX, 36 | path: [ 37 | ...network.shortestPath(nodes[0], nodes[4]), 38 | ...network.shortestPath(nodes[4], nodes[10]) 39 | ] 40 | })); 41 | this.vehicles.push(new NetworkSteeringVehicle(this, { 42 | speedMax: SPEED_MAX / 1.5, 43 | path: network.shortestPath(nodes[4], nodes[0]), 44 | })); 45 | this.vehicles.push(new NetworkSteeringVehicle(this, { 46 | speedMax: SPEED_MAX * 1.5, 47 | path: network.shortestPath(nodes[4], nodes[0]), 48 | })); 49 | 50 | // passing 51 | this.vehicles.push(new NetworkSteeringVehicle(this, { 52 | speedMax: SPEED_MAX, 53 | path: [ 54 | ...network.shortestPath(nodes[5], nodes[9]), 55 | ...network.shortestPath(nodes[9], nodes[10]) 56 | ] 57 | })); 58 | this.vehicles.push(new NetworkSteeringVehicle(this, { 59 | speedMax: SPEED_MAX / 2, 60 | path: network.shortestPath(nodes[6], nodes[9]), 61 | })); 62 | this.vehicles.push(new NetworkSteeringVehicle(this, { 63 | speedMax: SPEED_MAX / 3, 64 | path: network.shortestPath(nodes[7], nodes[9]), 65 | })); 66 | } else { 67 | 68 | // generate entities with random paths 69 | for (let i = 0; i < this.entityCount; i++) { 70 | this.vehicles.push(new NetworkSteeringVehicle(this)); 71 | } 72 | } 73 | 74 | // activate all vehicles 75 | this.vehicles.forEach(v => this.activate(v)); 76 | } 77 | } 78 | 79 | // Steering Vehicle that uses a SeekBehavior to travel on a network 80 | export class NetworkSteeringVehicle extends SteeringVehicle { 81 | path: ILink[] = []; 82 | 83 | constructor(simulation: NetworkSteering, options?: any) { 84 | super(); 85 | 86 | const 87 | sim = simulation, 88 | network = sim.network, 89 | nodes = network.nodes, 90 | speed = sim.speed.sample(); 91 | 92 | // initialize entity 93 | setOptions(this, { 94 | 95 | // simple properties 96 | color: 'orange', 97 | radius: 15, 98 | speedMin: SPEED_MIN, 99 | speedMax: speed, 100 | speed: speed, 101 | 102 | // behaviors 103 | behaviors: [ 104 | new NetworkAvoidBehavior({ 105 | avoidColor: 'red', 106 | obstacles: sim.vehicles, 107 | }), 108 | new NetworkSeekBehavior({ 109 | arrivalDistance: 5, // less than the vehicle radius 110 | maxSpeedDistance: 10, // shorter than a link 111 | arrive: () => { 112 | this.done = true; // entity has reached its target 113 | sim.vehiclesDone++; 114 | }, 115 | }), 116 | ] 117 | }); 118 | 119 | // apply options 120 | setOptions(this, options); 121 | 122 | // create a random path if we don't have one 123 | if (!this.path || this.path.length == 0) { 124 | let from = sim.rndNode.sample(); 125 | for (let i = 0; i < SEGMENT_COUNT; i++) { 126 | 127 | // select to nodes 128 | let to = sim.rndNode.sample(); 129 | while (to == from) { 130 | to = sim.rndNode.sample(); 131 | } 132 | 133 | // append to path 134 | this.path.push(...sim.network.shortestPath(nodes[from], nodes[to])); 135 | 136 | // start from current position 137 | from = to; 138 | } 139 | } 140 | } 141 | } 142 | 143 | /** 144 | * Behavior that causes entities to turn in order to overtake slower ones 145 | * and swerve to avoid head-on collisions with other entities. 146 | */ 147 | export class NetworkAvoidBehavior extends AvoidBehavior { 148 | applyBehavior(e: NetworkSteeringVehicle, dt: number): boolean { 149 | this.entity = e; 150 | 151 | // find nearest obstacle 152 | const obstacle = this.getNearestObstacle(dt, e.radius * 2); 153 | if (obstacle instanceof NetworkSteeringVehicle) { 154 | const 155 | link = e.path[0], 156 | dist = Point.distance(e.position, link.to.position), 157 | angDelta = Math.abs(e.angle - obstacle.angle); 158 | if (dist > 3 * e.radius) { // don't turn if too close to nodes 159 | 160 | // passing another vehicle 161 | const pass = obstacle.speed < e.speed && angDelta < 45; 162 | 163 | // swerve to avoid head-on collision 164 | const avoid = angDelta > 135; 165 | 166 | // change angle pass or avoid, slow down while avoiding 167 | if (pass || avoid) { 168 | 169 | // turn to pass and to avoid collisions 170 | const 171 | turnAngle = pass ? +10 : -30, // left to pass, right to avoid 172 | targetAngle = Point.angle(link.from.position, link.to.position) + turnAngle; 173 | e.angle = e.getTurnAngle(targetAngle, dt); 174 | 175 | // adjust speed 176 | if (!this.currentObstacle) { 177 | if (avoid) { // slow down if avoiding 178 | e.speed *= this.slowDown; 179 | } 180 | } 181 | 182 | // remember current obstacle 183 | this.currentObstacle = obstacle; 184 | return this.preventOthersWhileAvoiding; 185 | } 186 | } 187 | } 188 | 189 | // not in avoiding mode 190 | this.currentObstacle = null; 191 | return false; 192 | } 193 | } 194 | 195 | /** 196 | * Behavior that makes an entity traverse a path formed by a list 197 | * of {@link ILink} objects. 198 | */ 199 | export class NetworkSeekBehavior extends SeekBehavior { 200 | _lastPath: ILink[]; 201 | 202 | constructor(options: any) { 203 | super(); 204 | this.seekAngle = 5; // max turn per unit time 205 | setOptions(this, options); 206 | } 207 | 208 | applyBehavior(e: NetworkSteeringVehicle, dt: number): boolean { 209 | const path = e.path; 210 | if (path && path.length) { 211 | 212 | // save from position 213 | let link = path[0]; 214 | const startPosition = Point.clone(link.from.position); 215 | 216 | // merge with next link if the angle is the same 217 | const angle = Point.angle(link.from.position, link.to.position); 218 | if (path.length > 1) { 219 | const nextLink = path[1]; 220 | if (Point.angle(nextLink.from.position, nextLink.to.position) == angle) { 221 | link = nextLink; 222 | path.shift(); 223 | } 224 | } 225 | 226 | // update target 227 | this.target = link.to.position; 228 | 229 | // initialize position 230 | if (path != this._lastPath) { 231 | this._lastPath = path; 232 | e.position = startPosition; 233 | e.angle = angle; 234 | } 235 | 236 | // adjust speed 237 | const 238 | dist = Point.distance(e.position, this.target), 239 | distMax = this.maxSpeedDistance || (e.simulation.bounds[1].x / 2), 240 | pct = dist / distMax; 241 | e.speed = e.speedMax * pct; 242 | 243 | // get arrival distance 244 | let arrivalDistance = this.arrivalDistance != null 245 | ? this.arrivalDistance 246 | : e.radius; 247 | 248 | // adjust angle 249 | let angTarget = Point.angle(e.position, this.target); 250 | e.angle = e.getTurnAngle(angTarget, dt, this.seekAngle); 251 | 252 | // raise event on arrival 253 | if (dist < arrivalDistance) { 254 | path.shift(); 255 | if (path.length == 0) { 256 | this.onArrive(); 257 | } 258 | } 259 | } 260 | 261 | // done 262 | return false; 263 | } 264 | } -------------------------------------------------------------------------------- /simulations/simpletest.ts: -------------------------------------------------------------------------------- 1 | import { Simulation, FecItem } from '../simscript/simulation'; 2 | import { Entity } from '../simscript/entity'; 3 | import { Queue } from '../simscript/queue'; 4 | import { Uniform, Exponential } from '../simscript/random'; 5 | import { assert, setOptions } from '../simscript/util'; 6 | 7 | // Simple Test 8 | export class SimpleTest extends Simulation { 9 | qWait = new Queue('Wait'); 10 | qService = new Queue('Service', 1); 11 | onStarting() { 12 | super.onStarting(); 13 | 14 | console.log('activating SimpleEntity'); 15 | this.activate(new SimpleEntity()); 16 | 17 | console.log('generating 1000 SimplerEntity'); 18 | this.generateEntities(SimplerEntity, new Uniform(5, 10), 1000); 19 | } 20 | } 21 | class SimpleEntity extends Entity { 22 | async script() { 23 | const sim = this.simulation; 24 | 25 | // call a separate async method 26 | console.log('started at', sim.timeNow); 27 | await this.doSomeOtherStuff(); 28 | console.log('done some other stuff at', sim.timeNow); 29 | 30 | // now perform some simple tests 31 | let time = sim.timeNow; 32 | await this.enterQueue(sim.qWait); 33 | assert(time == sim.timeNow, 'no limit, no wait'); 34 | await this.delay(0); 35 | assert(time == sim.timeNow, 'no delay, no wait'); 36 | this.leaveQueue(sim.qWait); 37 | assert(time == sim.timeNow, 'no await, no wait'); 38 | console.log('left wait at', sim.timeNow); 39 | let noWait = sim.qService.pop == 0; 40 | time = sim.timeNow; 41 | await this.enterQueue(sim.qWait); 42 | await this.enterQueue(sim.qService); 43 | console.log('entered service at', sim.timeNow); 44 | this.leaveQueue(sim.qWait); 45 | console.log('left service at', sim.timeNow); 46 | if (noWait) { 47 | assert(time == sim.timeNow, 'no customer, no wait'); 48 | } 49 | time = sim.timeNow; 50 | await this.delay(10); 51 | this.leaveQueue(sim.qService); 52 | assert(sim.timeNow == time + 10, 'waited for 10 tu'); 53 | } 54 | async doSomeOtherStuff() { 55 | const sim = this.simulation as SimpleTest; 56 | const cnt = 10; 57 | const delay = 10; 58 | const t = this.simulation.timeNow; 59 | for (let i = 0; i < cnt; i++) { 60 | await this.delay(delay); 61 | } 62 | assert(this.simulation.timeNow == t + cnt * delay, 'should have waited (cnt * delay) tus'); 63 | console.log('other stuff done'); 64 | } 65 | 66 | } 67 | 68 | class SimplerEntity extends Entity { 69 | service = new Uniform(5, 10); 70 | async script() { 71 | let sim = this.simulation; 72 | await this.enterQueue(sim.qWait); 73 | await this.enterQueue(sim.qService); 74 | this.leaveQueue(sim.qWait); 75 | await this.delay(this.service.sample()); 76 | this.leaveQueue(sim.qService); 77 | } 78 | } 79 | 80 | export class SimplestSimulation extends Simulation { 81 | q = new Queue('simple'); 82 | onStarting() { 83 | super.onStarting(); 84 | this.activate(new SimplestReally()); 85 | } 86 | } 87 | class SimplestReally extends Entity { 88 | async script() { 89 | const sim = this.simulation; 90 | 91 | console.log('calling enterLeave', sim.timeNow, 'fec', sim._fec.length); 92 | await this.enterLeave(); 93 | console.log('returned from enterLeave', sim.timeNow); 94 | 95 | console.log('before delay 10', sim.timeNow); 96 | await this.delay(10); 97 | console.log('after delay 10', sim.timeNow); 98 | 99 | console.log('before delay 0', sim.timeNow); 100 | await this.delay(0); 101 | console.log('after delay 0', sim.timeNow); 102 | 103 | console.log('calling enterLeave', sim.timeNow, 'fec', sim._fec.length); 104 | await this.enterLeave(); 105 | console.log('returned from enterLeave', sim.timeNow); 106 | 107 | await this.enterQueue(sim.q); 108 | this.leaveQueue(sim.q); 109 | await this.enterQueue(sim.q); 110 | await this.delay(10); 111 | await this.delay(0); 112 | this.leaveQueue(sim.q); 113 | console.log('** SimplestReally done at', sim.timeNow); 114 | } 115 | 116 | async enterLeave() { 117 | const sim = this.simulation as SimplestSimulation; 118 | for (let i = 0; i < 10; i++) { 119 | await this.enterQueue(sim.q); 120 | await this.delay(0); 121 | this.leaveQueue(sim.q); 122 | console.log('loop', i, sim.timeNow); 123 | } 124 | 125 | // ** REVIEW ** 126 | // this is needed in case the async function doesn't 127 | // cause any simulated delays 128 | new FecItem(this, { ready: true }); 129 | } 130 | } 131 | 132 | export class Generator extends Simulation { 133 | cnt = 0; 134 | onStarting() { 135 | super.onStarting(); 136 | this.generateEntities(GeneratorEntity, 10, 100); 137 | } 138 | onFinished() { 139 | super.onFinished(); 140 | console.log('cnt is', this.cnt); 141 | console.log('elapsed', this.timeElapsed); 142 | } 143 | } 144 | class GeneratorEntity extends Entity { 145 | async script() { 146 | this.simulation.cnt++; 147 | console.log(' at', this.simulation.timeNow); 148 | } 149 | } 150 | 151 | // test interruptible delays 152 | export class Interrupt extends Simulation { 153 | q = new Queue('the queue'); 154 | delay = new Exponential(10); 155 | elapsed = 0; 156 | interrupted = 0; 157 | onStarting() { 158 | super.onStarting(); 159 | this.timeEnd = 10000; 160 | this.elapsed = 0; 161 | this.interrupted = 0; 162 | this.generateEntities(Interruptible, new Exponential(10)); 163 | this.generateEntities(Interruptor, new Exponential(10)); 164 | } 165 | } 166 | class Interruptible extends Entity { 167 | async script() { 168 | const sim = this.simulation; 169 | this.enterQueueImmediately(sim.q); 170 | const 171 | delay = sim.delay.sample(), 172 | timeSpent = await this.delay(delay, null, sim); 173 | if (Math.abs(timeSpent - delay) < 1e-10) { // account for floating point accuracy 174 | sim.elapsed++; 175 | } else { 176 | sim.interrupted++; 177 | } 178 | this.leaveQueue(sim.q); 179 | } 180 | } 181 | class Interruptor extends Entity { 182 | async script() { 183 | const sim = this.simulation; 184 | this.sendSignal(sim); 185 | await this.delay(sim.delay.sample()); 186 | this.sendSignal(sim); 187 | } 188 | } 189 | 190 | // test pre-empting enterQueue 191 | export class Preempt extends Simulation { 192 | resource = new Queue('resource', 1); 193 | q0 = new Queue('Prty 0'); 194 | q1 = new Queue('Prty 1'); 195 | q2 = new Queue('Prty 2'); 196 | onStarting() { 197 | super.onStarting(); 198 | this.activate(new Prty0({ 199 | priority: 0, 200 | start: 0, 201 | duration: 100 202 | })); 203 | this.activate(new Prty1({ 204 | priority: 1, 205 | start: 10, 206 | duration: 10 207 | })); 208 | this.activate(new Prty2({ 209 | priority: 2, 210 | start: 12, 211 | duration: 5 212 | })); 213 | } 214 | } 215 | class PreemptEntity extends Entity { 216 | start = 0; 217 | duration = 0; 218 | 219 | // constructor with extra options 220 | constructor(options?: any) { 221 | super(null); 222 | setOptions(this, options); 223 | } 224 | 225 | // log a message from an entity 226 | log(msg: string) { 227 | console.log(`${this.constructor.name} ${msg} at ${this.simulation.timeNow}`); 228 | } 229 | } 230 | class Prty0 extends PreemptEntity { 231 | async script() { 232 | const sim = this.simulation; 233 | this.log('arrived'); 234 | await this.delay(this.start); 235 | await this.seize(sim.resource, this.duration, sim.q0, sim.resource); 236 | assert(sim.timeNow == 115, 'should finish at 115'); 237 | this.log('done (@115)'); 238 | } 239 | } 240 | class Prty1 extends PreemptEntity { 241 | async script() { 242 | const sim = this.simulation; 243 | this.log('arrived'); 244 | await this.delay(this.start); 245 | await this.seize(sim.resource, this.duration, sim.q1, sim.resource); 246 | assert(sim.timeNow == 25, 'should finish at 25'); 247 | this.log('done (@25)'); 248 | } 249 | } 250 | class Prty2 extends PreemptEntity { 251 | async script() { 252 | const sim = this.simulation; 253 | this.log('arrived'); 254 | await this.delay(this.start); 255 | await this.seize(sim.resource, this.duration, sim.q2, sim.resource); 256 | assert(sim.timeNow == 17, 'should finish at 17'); 257 | this.log('done (@17)'); 258 | } 259 | } 260 | 261 | 262 | // M/M/1 263 | // https://en.wikipedia.org/wiki/M/M/1_queue 264 | export class MM1 extends Simulation { 265 | serviceTime: Exponential; 266 | interArrival: Exponential; 267 | q = new Queue('server', 1); 268 | system = new Queue('system'); 269 | onStarting() { 270 | super.onStarting(); 271 | this.timeUnit = 's'; 272 | this.serviceTime = new Exponential(10, 1); 273 | this.interArrival = new Exponential(15, 2); // different seeds to prevent correlation 274 | this.generateEntities(MM1Entity, this.interArrival, 1e6); 275 | } 276 | } 277 | class MM1Entity extends Entity { 278 | async script() { 279 | const sim = this.simulation; 280 | this.enterQueueImmediately(sim.system); 281 | await this.enterQueue(sim.q); 282 | await this.delay(sim.serviceTime.sample()); 283 | this.leaveQueue(sim.q); 284 | this.leaveQueue(sim.system); 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /simscript/docs/interfaces/ieventlistener.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | IEventListener | simscript 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 | 61 |

Interface IEventListener<S, T>

62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |

Type parameters

70 |
    71 |
  • 72 |

    S = any

    73 |
  • 74 |
  • 75 |

    T = EventArgs

    76 |
  • 77 |
78 |
79 |
80 |

Hierarchy

81 |
    82 |
  • 83 | IEventListener 84 |
  • 85 |
86 |
87 |
88 |

Callable

89 |
    90 |
  • IEventListener(sender: S, args: T): void
  • 91 |
92 |
    93 |
  • 94 | 99 |

    Parameters

    100 |
      101 |
    • 102 |
      sender: S
      103 |
    • 104 |
    • 105 |
      args: T
      106 |
    • 107 |
    108 |

    Returns void

    109 |
  • 110 |
111 |
112 |
113 | 244 |
245 |
246 |
247 |
248 |

Legend

249 |
250 |
    251 |
  • Constructor
  • 252 |
  • Property
  • 253 |
  • Method
  • 254 |
  • Accessor
  • 255 |
256 |
    257 |
  • Inherited method
  • 258 |
  • Inherited accessor
  • 259 |
260 |
    261 |
  • Static property
  • 262 |
  • Static method
  • 263 |
264 |
    265 |
  • Property
  • 266 |
267 |
268 |
269 |
270 |
271 |

Generated using TypeDoc

272 |
273 |
274 | 275 | 276 | -------------------------------------------------------------------------------- /simulations/car-follow-network.ts: -------------------------------------------------------------------------------- 1 | import { Simulation } from '../simscript/simulation'; 2 | import { Queue } from '../simscript/queue'; 3 | import { Entity, IAnimationPosition } from '../simscript/entity'; 4 | import { Network, INode, ILink } from '../simscript/network'; 5 | import { Uniform, Exponential, RandomInt } from '../simscript/random'; 6 | import { Point, IPoint } from '../simscript/util'; 7 | import { Event, EventArgs } from '../simscript/event'; 8 | 9 | interface ICar { 10 | speed: number; 11 | maxSpeed: number; 12 | accel: number; 13 | position: number; 14 | length: number; 15 | } 16 | 17 | export class CarFollowNetwork extends Simulation { 18 | timeIncrement = 1; // seconds 19 | totalCars = 1000; // number of cars to simulate 20 | carSpeeds = new Uniform(40 / 3.6, 100 / 3.6); // 40-100 km/h in m/s 21 | interArrival = new Exponential(15); // avg seconds between car arrivals 22 | network = createNetwork(5, 9, 100); // nodes are 100m apart 23 | rndNode = new RandomInt(this.network.nodes.length - 1); 24 | stats = { 25 | totalDistance: 0, 26 | totalTime: 0, 27 | carsDone: 0 28 | }; 29 | readonly carFinished = new Event(); 30 | 31 | onStarting(e) { 32 | this.stats.totalDistance = 0; 33 | this.stats.totalTime = 0; 34 | this.stats.carsDone = 0; 35 | 36 | super.onStarting(e); 37 | this.generateEntities(Car, this.interArrival, this.totalCars); 38 | } 39 | onCarFinished(e?: EventArgs) { 40 | this.carFinished.raise(this, e); 41 | } 42 | } 43 | 44 | export class Car extends Entity implements ICar { 45 | speed = 0; // starting speed 46 | accel = 10; // acceleration/deceleration 47 | position = 0; // current position 48 | length = 25; // vehicle length (not to scale) 49 | maxSpeed = 0; // random value from simulation 50 | delayStart = 0; // time when the entity started moving along the current link 51 | path: ILink[]; // current path 52 | 53 | async script() { 54 | const 55 | sim = this.simulation, 56 | network = sim.network, 57 | nodes = network.nodes, 58 | dt = sim.timeIncrement; 59 | 60 | // get this car's max speed 61 | this.maxSpeed = sim.carSpeeds.sample(); 62 | 63 | // select from/to nodes 64 | let 65 | ndFrom = nodes[0], 66 | ndTo = nodes[nodes.length - 1], 67 | position = 0; 68 | 69 | // travel one link at a time, 70 | // re-computing the path to avoid congestion 71 | while (ndFrom != ndTo) { 72 | const timeStarted = sim.timeNow; 73 | 74 | // find the path 75 | this.path = network.shortestPath(ndFrom, ndTo); 76 | 77 | // move along the link 78 | const link = this.path[0]; 79 | const length = sim.network.getLinkDistance(link); 80 | 81 | // enter link 82 | this.position = position; 83 | this.enterQueueImmediately(link.queue); 84 | 85 | // move along the link 86 | while (this.position < length) { 87 | this.speed = this.getSpeed(dt); 88 | this.delayStart = sim.timeNow; // used by getAnimatinPosition 89 | await this.delay(dt); 90 | this.position += this.speed * dt; 91 | } 92 | 93 | // leave link 94 | this.leaveQueue(link.queue); 95 | 96 | // ready for the next link 97 | ndFrom = link.to; 98 | position = Math.max(0, this.position - length); 99 | 100 | // update link simulation stats 101 | sim.stats.totalDistance += Point.distance(link.from.position, link.to.position); 102 | sim.stats.totalTime += sim.timeNow - timeStarted; 103 | } 104 | 105 | // update car count simulation stats 106 | sim.stats.carsDone++; 107 | sim.onCarFinished(); 108 | } 109 | 110 | // gets the car's animation position and angle 111 | getAnimationPosition(q: Queue, start: IPoint, end: IPoint): IAnimationPosition { 112 | const 113 | sim = this.simulation, 114 | len = sim.network.getLinkDistance(this.path[0]), 115 | pos = this.position + (sim.timeNow - this.delayStart) * this.speed, 116 | pt = Point.interpolate(start, end, pos / len); 117 | return { 118 | position: pt, 119 | angle: Point.angle(start, end, false) 120 | } 121 | } 122 | 123 | // gets the vehicle speed taking into account the max safe speed 124 | getSpeed(dt: number): number { 125 | const safeSpeed = Math.min(this.getSafeSpeed(dt), this.maxSpeed); 126 | if (safeSpeed > this.speed) { // accelerate 127 | return Math.min(safeSpeed, this.speed + this.accel * dt); 128 | } 129 | if (safeSpeed < this.speed) { // decelerate 130 | return Math.max(safeSpeed, this.speed - this.accel * dt); 131 | } 132 | return this.speed; // no change 133 | } 134 | 135 | // gets the speed that would allow this vehicle to stop 136 | // before hitting the vehicle ahead if it were to stop. 137 | getSafeSpeed(dt: number): number { 138 | 139 | // assume max speed 140 | let speed = this.maxSpeed; 141 | 142 | // get vehicle ahead of us (or end of the road) 143 | const vAhead = this.getCarAhead() as ICar; 144 | if (vAhead != null) { 145 | 146 | // calculate vehicle ahead's breaking distance 147 | const dAhead = Math.max(0, vAhead.position - this.position - vAhead.length); 148 | let breakingDistance = dAhead; 149 | if (vAhead.speed && vAhead.accel) { 150 | breakingDistance += (vAhead.speed * vAhead.speed) / (2 * vAhead.accel); 151 | } 152 | 153 | // calculate max speed that allows us to break 154 | const rad = dt * dt / 4 - (this.speed * dt - 2 * breakingDistance) / this.accel; 155 | speed = rad > 0 156 | ? +this.accel * (Math.sqrt(rad) - dt / 2) 157 | : -this.accel * dt / 2; // no time to stop, negative speed... 158 | } 159 | 160 | // done 161 | return Math.max(0, speed); 162 | } 163 | 164 | // gets the car that is ahead of this one 165 | getCarAhead(): ICar { 166 | const 167 | sim = this.simulation, 168 | link = this.path[0], 169 | q = link.queue; 170 | 171 | // index 0 is the first car 172 | const index = q.entities.indexOf(this); 173 | if (index > 0) { 174 | return q.entities[index - 1] as Car; 175 | } 176 | 177 | // look at the first car in the next link 178 | if (this.path.length > 1) { 179 | const qAhead = this.path[1].queue; 180 | if (qAhead.pop) { 181 | const carAhead = qAhead.entities[0] as Car; 182 | return { 183 | speed: carAhead.speed, 184 | maxSpeed: carAhead.maxSpeed, 185 | accel: carAhead.maxSpeed, 186 | position: this.position + carAhead.position, 187 | length: carAhead.length 188 | } 189 | } else { 190 | return null; // no cars in the next link 191 | } 192 | } 193 | 194 | // no vehicle ahead, stop at the end of the link 195 | return { 196 | speed: 0, 197 | maxSpeed: 0, 198 | accel: 0, 199 | position: sim.network.getLinkDistance(link), 200 | length: 0 201 | }; 202 | } 203 | } 204 | 205 | // network with congestion cost 206 | class CongestionNetwork extends Network { 207 | getLinkDistance(link: ILink, prevLink?: ILink): number { 208 | let dist = super.getLinkDistance(link, prevLink); 209 | dist += dist * link.queue.pop * 0.5; // add some congestion cost 210 | return dist; 211 | } 212 | } 213 | 214 | // create a grid-like network 215 | function createNetwork(rows: number, cols: number, spacing: number) { 216 | 217 | // create nodes 218 | const nodes: INode[] = []; 219 | for (let r = 0; r < rows; r++) { 220 | for (let c = 0; c < cols; c++) { 221 | nodes.push({ 222 | id: nodes.length.toString(), 223 | position: { x: c * spacing, y: r * spacing }, 224 | queue: new Queue() 225 | }); 226 | } 227 | } 228 | 229 | // create links 230 | const links: ILink[] = []; 231 | for (let i = 0; i < nodes.length; i++) { 232 | const row = Math.floor(i / cols); 233 | const col = i % cols; 234 | 235 | // grid links 236 | if (col < cols - 1) { // right/left 237 | links.push({ from: nodes[i], to: nodes[i + 1], queue: new Queue() }); 238 | links.push({ from: nodes[i + 1], to: nodes[i], queue: new Queue() }); 239 | } 240 | if (row < rows - 1 && (i % 2 != 0)) { // up/down, sparse 241 | links.push({ from: nodes[i], to: nodes[i + cols], queue: new Queue() }); 242 | links.push({ from: nodes[i + cols], to: nodes[i], queue: new Queue() }); 243 | } 244 | 245 | // diagonal links 246 | if (row == 0 && col == 0) { 247 | links.push({ from: nodes[i + 1], to: nodes[i + cols], queue: new Queue() }); 248 | links.push({ from: nodes[i + cols], to: nodes[i + 1], queue: new Queue() }); 249 | } 250 | if (row == 0 && col == cols - 1) { 251 | links.push({ from: nodes[i - 1], to: nodes[i + cols], queue: new Queue() }); 252 | links.push({ from: nodes[i + cols], to: nodes[i - 1], queue: new Queue() }); 253 | } 254 | if (row == rows - 1 && col == 0) { 255 | links.push({ from: nodes[i + 1], to: nodes[i - cols], queue: new Queue() }); 256 | links.push({ from: nodes[i - cols], to: nodes[i + 1], queue: new Queue() }); 257 | } 258 | if (row == rows - 1 && col == cols - 1) { 259 | links.push({ from: nodes[i - 1], to: nodes[i - cols], queue: new Queue() }); 260 | links.push({ from: nodes[i - cols], to: nodes[i - 1], queue: new Queue() }); 261 | } 262 | } 263 | 264 | // return the network 265 | return new CongestionNetwork({ 266 | nodes: nodes, 267 | links: links, 268 | }); 269 | } 270 | 271 | // renders a network into an x3d element 272 | function renderNetworkX3D(network: Network, x3d: HTMLElement) { 273 | const scene = x3d.querySelector('scene'); 274 | let html = ''; 275 | network.nodes.forEach(nd => { 276 | const pos = nd.position; 277 | html += ` 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | `; 286 | }); 287 | network.links.forEach((link: ILink, index: number) => { 288 | if (index % 2 == 0) { 289 | const from = link.from.position; 290 | const to = link.to.position; 291 | const len = Point.distance(from, to); 292 | html += ` 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | `; 303 | } 304 | }); 305 | scene.innerHTML += html; 306 | } 307 | -------------------------------------------------------------------------------- /simscript/docs/interfaces/ianimationposition.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | IAnimationPosition | simscript 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 | 61 |

Interface IAnimationPosition

62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |

Defines parameters for animating entities in queues.

72 |
73 |
74 |
75 |
76 |

Hierarchy

77 |
    78 |
  • 79 | IAnimationPosition 80 |
  • 81 |
82 |
83 |
84 |

Index

85 |
86 |
87 |
88 |

Properties

89 | 93 |
94 |
95 |
96 |
97 |
98 |

Properties

99 |
100 | 101 |

angle

102 |
angle: number
103 | 108 |
109 |
110 |

Entity rotation angle, in degrees, measured clockwise 111 | from the nine o'clock position.

112 |
113 |
114 |
115 |
116 | 117 |

position

118 |
position: IPoint
119 | 124 |
125 |
126 |

Entity position in the Animation.

127 |
128 |
129 |
130 |
131 |
132 | 271 |
272 |
273 |
274 |
275 |

Legend

276 |
277 |
    278 |
  • Constructor
  • 279 |
  • Property
  • 280 |
  • Method
  • 281 |
  • Accessor
  • 282 |
283 |
    284 |
  • Inherited method
  • 285 |
  • Inherited accessor
  • 286 |
287 |
    288 |
  • Static property
  • 289 |
  • Static method
  • 290 |
291 |
    292 |
  • Property
  • 293 |
294 |
295 |
296 |
297 |
298 |

Generated using TypeDoc

299 |
300 |
301 | 302 | 303 | -------------------------------------------------------------------------------- /simscript/docs/classes/eventargs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | EventArgs | simscript 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 | 61 |

Class EventArgs

62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |

Represents the parameters passed in to listeners attached to an Event.

72 |
73 |
74 |
75 |
76 |

Hierarchy

77 |
    78 |
  • 79 | EventArgs 80 |
  • 81 |
82 |
83 |
84 |

Index

85 |
86 |
87 |
88 |

Constructors

89 | 92 |
93 |
94 |

Properties

95 | 98 |
99 |
100 |
101 |
102 |
103 |

Constructors

104 |
105 | 106 |

constructor

107 | 110 |
    111 |
  • 112 | 114 |

    Returns EventArgs

    115 |
  • 116 |
117 |
118 |
119 |
120 |

Properties

121 |
122 | 123 |

Static empty

124 |
empty: EventArgs = ...
125 | 130 |
131 |
132 |
133 | 272 |
273 |
274 |
275 |
276 |

Legend

277 |
278 |
    279 |
  • Constructor
  • 280 |
  • Property
  • 281 |
  • Method
  • 282 |
  • Accessor
  • 283 |
284 |
    285 |
  • Inherited method
  • 286 |
  • Inherited accessor
  • 287 |
288 |
    289 |
  • Static property
  • 290 |
  • Static method
  • 291 |
292 |
    293 |
  • Property
  • 294 |
295 |
296 |
297 |
298 |
299 |

Generated using TypeDoc

300 |
301 |
302 | 303 | 304 | -------------------------------------------------------------------------------- /simscript/docs/interfaces/ihistogramentry.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | IHistogramEntry | simscript 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 | 61 |

Interface IHistogramEntry

62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |

Represents an entry in a histogram created by the 72 | Tally.getHistogram method.

73 |
74 |
75 |
76 |
77 |

Hierarchy

78 |
    79 |
  • 80 | IHistogramEntry 81 |
  • 82 |
83 |
84 |
85 |

Index

86 |
87 |
88 |
89 |

Properties

90 | 95 |
96 |
97 |
98 |
99 |
100 |

Properties

101 |
102 | 103 |

count

104 |
count: number
105 | 110 |
111 |
112 | 113 |

from

114 |
from: number
115 | 120 |
121 |
122 | 123 |

to

124 |
to: number
125 | 130 |
131 |
132 |
133 | 275 |
276 |
277 |
278 |
279 |

Legend

280 |
281 |
    282 |
  • Constructor
  • 283 |
  • Property
  • 284 |
  • Method
  • 285 |
  • Accessor
  • 286 |
287 |
    288 |
  • Inherited method
  • 289 |
  • Inherited accessor
  • 290 |
291 |
    292 |
  • Static property
  • 293 |
  • Static method
  • 294 |
295 |
    296 |
  • Property
  • 297 |
298 |
299 |
300 |
301 |
302 |

Generated using TypeDoc

303 |
304 |
305 | 306 | 307 | -------------------------------------------------------------------------------- /simscript/docs/interfaces/ipoint.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | IPoint | simscript 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 | 61 |

Interface IPoint

62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |

Defines the properties of point objects.

72 |
73 |
74 |
75 |
76 |

Hierarchy

77 |
    78 |
  • 79 | IPoint 80 |
  • 81 |
82 |
83 |
84 |

Implemented by

85 | 88 |
89 |
90 |

Index

91 |
92 |
93 |
94 |

Properties

95 |
    96 |
  • x
  • 97 |
  • y
  • 98 |
  • z
  • 99 |
100 |
101 |
102 |
103 |
104 |
105 |

Properties

106 |
107 | 108 |

x

109 |
x: number
110 | 115 |
116 |
117 | 118 |

y

119 |
y: number
120 | 125 |
126 |
127 | 128 |

Optional z

129 |
z: number
130 | 135 |
136 |
137 |
138 | 280 |
281 |
282 |
283 |
284 |

Legend

285 |
286 |
    287 |
  • Constructor
  • 288 |
  • Property
  • 289 |
  • Method
  • 290 |
  • Accessor
  • 291 |
292 |
    293 |
  • Inherited method
  • 294 |
  • Inherited accessor
  • 295 |
296 |
    297 |
  • Static property
  • 298 |
  • Static method
  • 299 |
300 |
    301 |
  • Property
  • 302 |
303 |
304 |
305 |
306 |
307 |

Generated using TypeDoc

308 |
309 |
310 | 311 | 312 | -------------------------------------------------------------------------------- /simscript/network.ts: -------------------------------------------------------------------------------- 1 | import { assert, setOptions, IPoint, Point } from './util'; 2 | import { Queue } from './queue'; 3 | 4 | /** 5 | * Defines properties for network nodes. 6 | */ 7 | export interface INode { 8 | /** Gets or sets the node's ID. */ 9 | id?: string; 10 | /** Gets or sets the node's position. */ 11 | position?: IPoint; 12 | /** Gets or sets the {@link Queue} associated with the node. */ 13 | queue?: Queue; 14 | } 15 | 16 | /** 17 | * Defines properties for network links. 18 | */ 19 | export interface ILink { 20 | /** Gets or sets the link's ID. */ 21 | id?: string; 22 | /** Gets or sets the link's **from** {@link INode}. */ 23 | from: INode; 24 | /** Gets or sets the link's **to** {@link INode}. */ 25 | to: INode; 26 | /** 27 | * Gets or sets the link's distance. 28 | * If ommitted, the distance is calculated based on the position 29 | * of the **from** and **to** nodes. */ 30 | distance?: number; 31 | /** 32 | * Gets or sets a value that determines whether the link is 33 | * currently disabled. 34 | */ 35 | disabled?: boolean; 36 | /** Gets or sets the {@link Queue} associated with the link. */ 37 | queue?: Queue; 38 | } 39 | 40 | // Defines properties for path segments. 41 | interface PathPart { 42 | id?: string; 43 | node: INode, 44 | link: ILink, 45 | distance: number, 46 | }; 47 | 48 | /** 49 | * Represents a network defined by an array of {@link INode} and an 50 | * array of {@link ILink} elements along which entities may move. 51 | * 52 | * Networks can be used to simulate streets, rails, rivers, or any 53 | * other system with nodes connected by links. 54 | * 55 | * Use networks to select the shortest path between two points in 56 | * the simulation, them move entities along the path using the entity's 57 | * {@link Entity.delay} method. 58 | * 59 | * For an example, please see the {@link shortestPath} method. 60 | * 61 | * Networks are defined by arrays of {@link INode} and {@link ILink} 62 | * objects. 63 | * 64 | * Both {@link INode} and {@link ILink} objects may optionally 65 | * include {@link Queue} objects. These queues are not used by the 66 | * {@link Network} class, but may be used by calling simulations to 67 | * control the flow of entities along the network. 68 | * 69 | * For an example of using link queues to account for congestion, 70 | * please see the {@link getLinkDistance} method. 71 | */ 72 | export class Network { 73 | _nodes: INode[]; 74 | _links: ILink[]; 75 | 76 | /** 77 | * Initializes a new instance of the {@link Network} class. 78 | * 79 | * @param options Object with parameters used to initialize the {@link Network}. 80 | */ 81 | constructor(options?: any) { 82 | setOptions(this, options); 83 | } 84 | 85 | /** 86 | * Gets or sets the network nodes. 87 | */ 88 | set nodes(value: INode[]) { 89 | this._nodes = value; 90 | } 91 | get nodes(): INode[] { 92 | return this._nodes; 93 | } 94 | /** 95 | * Gets or sets the network links. 96 | */ 97 | set links(value: ILink[]) { 98 | this._links = value; 99 | } 100 | get links(): ILink[] { 101 | return this._links; 102 | } 103 | /** 104 | * Gets the node with a given ID. 105 | * @param id ID of the node to get. 106 | */ 107 | getNode(id: any): INode { 108 | id = id.toString(); 109 | return this._nodes.find(nd => nd.id == id); 110 | } 111 | /** 112 | * Computes the shortest path between two nodes on the network. 113 | * 114 | * The **shortestPath** method returns an array of {@link ILink} 115 | * objects that represent the shortest path between two network 116 | * {@link ILink} objects. 117 | * 118 | * If a path between the nodes cannot be found, the method returns 119 | * an empty array. 120 | * 121 | * The example below shows how you to move an {@link Entity} 122 | * between to nodes on a {@link Network}: 123 | * 124 | * ```typescript 125 | * class Vehicle extends Entity { 126 | * async script() { 127 | * 128 | * // get a reference to the simulation 129 | * const sim = this.simulation as NetworkIntro; 130 | * 131 | * // select from/to nodes 132 | * const from = sim.network.nodes[0]; 133 | * const to = sim.network.nodes[10]; 134 | * 135 | * // calculate the shortest path 136 | * const path = sim.network.shortestPath(from, to); 137 | * assert(path.length > 0, 'cannot reach destination'); 138 | * 139 | * // move along the path one link at a time 140 | * for (let i = 0; i < path.length; i++) { 141 | * const link = path[i]; 142 | * const distance = sim.network.getLinkDistance(link, i > 0 ? path[i - 1] : null); 143 | * const time = distance / sim.serviceVehicleSpeed.sample(); 144 | * await this.delay(time, { 145 | * queues: [link.from.queue, link.to.queue] // used in animations 146 | * }); 147 | * } 148 | * } 149 | * } 150 | * ``` 151 | * 152 | * You can also use the {@link mergePath} method to get a list of queues 153 | * that make up the path and the total path distance, and use those 154 | * elements to travel the entire path with a single delay: 155 | * 156 | * ```typescript 157 | * // calculate the shortest path 158 | * const path = sim.network.shortestPath(from, to); 159 | * assert(path.length > 0, 'cannot reach destination'); 160 | * 161 | * // merge the path into a list of queues 162 | * const [queues, distance] = sim.network.mergePath(path); 163 | * 164 | * // traverse the whole path with a single delay 165 | * await this.delay(distance / sim.speed.sample(), { 166 | * queues: queues, 167 | * tension: 0.5 168 | * }); 169 | * ``` 170 | * 171 | * @param start Start {@link INode}. 172 | * @param finish Finish {@link INode}. 173 | * @returns An array of {@link ILink} objects that describe the 174 | * shortest path from **start** to **finish**, or an empty array 175 | * if a path could not be found. 176 | */ 177 | shortestPath(start: INode, finish: INode): ILink[] { 178 | 179 | // ** REVIEW: switch to A* for efficiency? 180 | 181 | // create the unvisited list with the following headings 182 | const 183 | unvisited: PathPart[] = [], 184 | visited: PathPart[] = []; 185 | this._nodes.forEach(node => { 186 | unvisited.push({ 187 | id: node.id, 188 | node: node, 189 | distance: node == start ? 0 : Infinity, // starting node has distance/cost zero 190 | link: null 191 | }); 192 | }); 193 | 194 | // build path 195 | while (unvisited.length) { 196 | 197 | // find the unvisited node that has the lowest distance and make it current 198 | let current: PathPart = null; 199 | unvisited.forEach(pp => { 200 | if (current == null || pp.distance < current.distance) { 201 | current = pp; 202 | } 203 | }); 204 | 205 | // move current node from unvisited to visited 206 | unvisited.splice(unvisited.indexOf(current), 1); 207 | visited.push(current); 208 | 209 | // examine the nodes that can be reached directly from the current node 210 | // that have not yet been visited 211 | if (unvisited.length) { 212 | this._links.forEach(link => { 213 | if (link.from == current.node && !link.disabled) { 214 | unvisited.forEach(pp => { 215 | if (pp.node == link.to) { 216 | const distance = current.distance + this.getLinkDistance(link, current.link); 217 | if (distance < pp.distance) { 218 | pp.distance = distance; 219 | pp.link = link; 220 | } 221 | } 222 | }); 223 | } 224 | }); 225 | } 226 | } 227 | 228 | // done, retrieve path as a list of nodes 229 | const linkPath: ILink[] = []; 230 | for (let node = finish; node != null;) { 231 | const 232 | part = this._getPathPart(visited, node), 233 | link = part.link; 234 | if (!link) { 235 | break; 236 | } 237 | node = link.from; 238 | linkPath.unshift(link); 239 | } 240 | return linkPath; 241 | } 242 | /** 243 | * Gets the distance (cost) associated with a link. 244 | * 245 | * The function returns the link's **distance** value, if specified, 246 | * or the distance between the link's **from** and **to** nodes. 247 | * 248 | * You can override this method to account for congestion along 249 | * links, turning costs, or temporary flow restrictions. 250 | * 251 | * For example, the code below creates a **CongestionNetwork** 252 | * class that accounts for congestion by increasing the calculated 253 | * distance based on the link's current population: 254 | * 255 | * ```typescript 256 | * // network with congestion cost 257 | * // assumes the simulation adds entities to the link's queue 258 | * // while they move along each link. 259 | * class CongestionNetwork extends Network { 260 | * getLinkDistance(link: ILink, prevLink?: ILink): number { 261 | * 262 | * // regular distance 263 | * let dist = super.getLinkDistance(link, prevLink); 264 | * 265 | * // add congestion cost 266 | * dist += dist * link.queue.pop * 0.5; 267 | * 268 | * // add turning cost 269 | * if (prevLink) { 270 | * const 271 | * a1 = this._getLinkAngle(link), 272 | * a2 = this._getLinkAngle(prevLink); 273 | * let angle = Math.abs(a1 - a2); // turn in radians 274 | * distance += someFunction(angle); 275 | * } 276 | * 277 | * // done 278 | * return dist; 279 | * } 280 | * _getLinkAngle(link: ILink): number { 281 | * return Point.angle(link.from.position, link.to.position, true); 282 | * } 283 | * } 284 | * ``` 285 | * @param link Link whose distance is being evaluated. 286 | * @param prevLink Previous link in the path (used to calculate turning costs). 287 | * @returns The distance (cost) associated with the link. 288 | */ 289 | getLinkDistance(link: ILink, prevLink?: ILink): number { 290 | let distance = null; 291 | 292 | // calculate link distance/cost 293 | if (link.distance != null) { 294 | distance = link.distance; 295 | } else { 296 | assert(link.from.position != null && link.to.position != null, 'link must have a distance or connect points with positions'); 297 | distance = Point.distance(link.from.position, link.to.position); 298 | } 299 | 300 | /* 301 | // add turning cost 302 | if (prevLink) { 303 | const 304 | a1 = this._getLinkAngle(link), 305 | a2 = this._getLinkAngle(prevLink); 306 | let angle = Math.abs(a1 - a2) / Math.PI * 180; // turn in radians 307 | distance += f(angle); 308 | } 309 | */ 310 | 311 | // done 312 | return distance; 313 | } 314 | /** 315 | * Merges a path defined by an array of {@link ILink} objects into an 316 | * array of {@link Queue} objects. 317 | * 318 | * This makes it possible for entities to traverse the whole path 319 | * with a single delay. For example: 320 | * 321 | * ```typescript 322 | * // select from/to nodes 323 | * const from = sim.network.nodes[0]; 324 | * const to = sim.network.nodes[10]; 325 | * 326 | * // get shortest path 327 | * const path = sim.network.shortestPath(from, to); 328 | * 329 | * // merge the path into a list of queues 330 | * const [queues, distance] = sim.network.mergePath(path); 331 | * 332 | * // traverse the whole path with a single delay 333 | * await this.delay(distance / sim.speed.sample(), { 334 | * queues: queues, 335 | * tension: 0.5 336 | * }); 337 | * ``` 338 | * 339 | * @param path Array of {@link ILink} objects that defines the path. 340 | * @returns An array where the first element is an array of 341 | * {@link Queue} objects to be visited and the second is the total 342 | * path distance. 343 | */ 344 | mergePath(path: ILink[]): [Queue[], number] { 345 | const queues = []; 346 | let dist = 0; 347 | path.forEach((link: ILink, i: number) => { 348 | dist += this.getLinkDistance(link, i > 0 ? path[i - 1] : null); 349 | if (i == 0) { 350 | queues.push(link.from.queue); 351 | } 352 | queues.push(link.to.queue); 353 | }); 354 | return [queues, dist]; 355 | } 356 | 357 | // ** implementation 358 | 359 | _getLinkAngle(link: ILink): number { 360 | return Point.angle(link.from.position, link.to.position, true); 361 | } 362 | _getPathPart(visited: PathPart[], node: INode): PathPart { 363 | for (let i = 0; i < visited.length; i++) { 364 | let pp = visited[i]; 365 | if (pp.node == node) { 366 | return pp; 367 | } 368 | } 369 | return null; 370 | } 371 | } -------------------------------------------------------------------------------- /simscript/docs/interfaces/inode.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | INode | simscript 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 | 61 |

Interface INode

62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |

Defines properties for network nodes.

72 |
73 |
74 |
75 |
76 |

Hierarchy

77 |
    78 |
  • 79 | INode 80 |
  • 81 |
82 |
83 |
84 |

Index

85 |
86 |
87 |
88 |

Properties

89 | 94 |
95 |
96 |
97 |
98 |
99 |

Properties

100 |
101 | 102 |

Optional id

103 |
id: string
104 | 109 |
110 |
111 |

Gets or sets the node's ID.

112 |
113 |
114 |
115 |
116 | 117 |

Optional position

118 |
position: IPoint
119 | 124 |
125 |
126 |

Gets or sets the node's position.

127 |
128 |
129 |
130 |
131 | 132 |

Optional queue

133 |
queue: Queue
134 | 139 |
140 |
141 |

Gets or sets the Queue associated with the node.

142 |
143 |
144 |
145 |
146 |
147 | 289 |
290 |
291 |
292 |
293 |

Legend

294 |
295 |
    296 |
  • Constructor
  • 297 |
  • Property
  • 298 |
  • Method
  • 299 |
  • Accessor
  • 300 |
301 |
    302 |
  • Inherited method
  • 303 |
  • Inherited accessor
  • 304 |
305 |
    306 |
  • Static property
  • 307 |
  • Static method
  • 308 |
309 |
    310 |
  • Property
  • 311 |
312 |
313 |
314 |
315 |
316 |

Generated using TypeDoc

317 |
318 |
319 | 320 | 321 | -------------------------------------------------------------------------------- /simscript/docs/enums/simulationstate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SimulationState | simscript 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 | 61 |

Enumeration SimulationState

62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |

Represents the current simulation state.

72 |
73 |
74 |
75 |
76 |

Index

77 |
78 |
79 |
80 |

Enumeration members

81 | 86 |
87 |
88 |
89 |
90 |
91 |

Enumeration members

92 |
93 | 94 |

Finished

95 |
Finished: = 1
96 | 101 |
102 |
103 |

The simulation has finished execution. 104 | It can be re-started by calling the Simulation.start method.

105 |
106 |
107 |
108 |
109 | 110 |

Paused

111 |
Paused: = 0
112 | 117 |
118 |
119 |

The simulation is paused but has not finished yet. 120 | It can be started by calling the Simulation.start method.

121 |
122 |
123 |
124 |
125 | 126 |

Running

127 |
Running: = 2
128 | 133 |
134 |
135 |

The simulation is running. 136 | It can be stopped by calling the Simulation.stop method.

137 |
138 |
139 |
140 |
141 |
142 | 284 |
285 |
286 |
287 |
288 |

Legend

289 |
290 |
    291 |
  • Constructor
  • 292 |
  • Property
  • 293 |
  • Method
  • 294 |
  • Accessor
  • 295 |
296 |
    297 |
  • Inherited method
  • 298 |
  • Inherited accessor
  • 299 |
300 |
    301 |
  • Static property
  • 302 |
  • Static method
  • 303 |
304 |
    305 |
  • Property
  • 306 |
307 |
308 |
309 |
310 |
311 |

Generated using TypeDoc

312 |
313 |
314 | 315 | 316 | --------------------------------------------------------------------------------