diff --git a/cmd/tools/modules/testing/common.v b/cmd/tools/modules/testing/common.v
index d11b8c9826..ac34462065 100644
--- a/cmd/tools/modules/testing/common.v
+++ b/cmd/tools/modules/testing/common.v
@@ -164,6 +164,14 @@ pub fn new_test_session(_vargs string, will_compile bool) TestSession {
skip_files << 'examples/database/orm.v' // try fix it
}
}
+ $if windows {
+ // TODO: remove when closures on windows are supported
+ skip_files << 'examples/pendulum-simulation/animation.v'
+ skip_files << 'examples/pendulum-simulation/full.v'
+ skip_files << 'examples/pendulum-simulation/parallel.v'
+ skip_files << 'examples/pendulum-simulation/parallel_with_iw.v'
+ skip_files << 'examples/pendulum-simulation/sequential.v'
+ }
if testing.github_job != 'sokol-shaders-can-be-compiled' {
// These examples need .h files that are produced from the supplied .glsl files,
// using by the shader compiler tools in https://github.com/floooh/sokol-tools-bin/archive/pre-feb2021-api-changes.tar.gz
diff --git a/examples/pendulum-simulation/.gitignore b/examples/pendulum-simulation/.gitignore
new file mode 100644
index 0000000000..5e967630af
--- /dev/null
+++ b/examples/pendulum-simulation/.gitignore
@@ -0,0 +1,18 @@
+# Executables files
+test
+test.exe
+
+# Temporary files
+fns.txt
+
+!/bin/test
+
+/docs/build
+*.ppm
+main
+parallel
+parallel_with_iw
+sequential
+animation
+full
+*.log
diff --git a/examples/pendulum-simulation/LICENSE b/examples/pendulum-simulation/LICENSE
new file mode 100644
index 0000000000..1d17ebf251
--- /dev/null
+++ b/examples/pendulum-simulation/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021-2022 Ulises Jeremias Cornejo Fandos
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/examples/pendulum-simulation/README.md b/examples/pendulum-simulation/README.md
new file mode 100644
index 0000000000..bc1e67b5fa
--- /dev/null
+++ b/examples/pendulum-simulation/README.md
@@ -0,0 +1,93 @@
+
+
+
+
+
+
Pendulum Simulation in V
+
+You can see the origin implementation among with some benchmarks at
+[ulises-jeremias/v-pendulum-simulation](https://github.com/ulises-jeremias/v-pendulum-simulation).
+
+[vlang.io](https://vlang.io) |
+[Docs](https://ulises-jeremias.github.io/v-pendulum-simulation) |
+[Contributing](https://github.com/ulises-jeremias/v-pendulum-simulation/blob/main/CONTRIBUTING.md)
+
+
+
+
+[![Build Status][workflowbadge]][workflowurl]
+[![Docs Validation][validatedocsbadge]][validatedocsurl]
+[![License: MIT][licensebadge]][licenseurl]
+
+
+
+## Run the Simulations
+
+### Sequential Simulation
+
+```sh
+$ v -gc boehm -prod sequential.v
+$ ./sequential # execute ./sequential -h for more info
+```
+
+### Parallel Simulation
+
+```sh
+$ v -gc boehm -prod parallel.v
+$ ./parallel # execute ./parallel -h for more info
+```
+
+![image](https://user-images.githubusercontent.com/17727170/153718774-1c93b158-aee3-4be1-bb47-fe601fed7336.png)
+
+### Parallel Simulation with Image Worker
+
+```sh
+$ v -gc boehm -prod parallel_with_iw.v
+$ ./parallel_with_iw # execute ./parallel_with_iw -h for more info
+```
+
+![image](https://user-images.githubusercontent.com/17727170/153718769-eabb334d-454f-469f-a51a-14ffe67507de.png)
+
+### Parallel Simulation with Graphic User Interface
+
+```sh
+$ v -gc boehm -prod animation.v
+$ ./animation # execute ./animation -h for more info
+```
+
+### Full Parallel Simulation with Graphic User Interface and Image Output
+
+```sh
+$ v -gc boehm -prod full.v
+$ ./full # execute ./full -h for more info
+```
+
+## Testing
+
+To test the module, just type the following command:
+
+```sh
+$ v test .
+```
+
+## Benchmark
+
+Check the original repository for tools to run benchmark tests. In there you can execute
+the following command to execute benchmark tests to get a full comparison between implementations:
+
+```sh
+$ ./bin/run-benchmark-test --help
+```
+
+![image](https://user-images.githubusercontent.com/17727170/152750137-98e7c5a3-936b-4bc8-b71a-1b182c0bbf50.png)
+
+[workflowbadge]: https://github.com/ulises-jeremias/v-pendulum-simulation/workflows/Build%20and%20Test%20with%20deps/badge.svg
+[validatedocsbadge]: https://github.com/ulises-jeremias/v-pendulum-simulation/workflows/Validate%20Docs/badge.svg
+[licensebadge]: https://img.shields.io/badge/License-MIT-blue.svg
+[workflowurl]: https://github.com/ulises-jeremias/v-pendulum-simulation/commits/main
+[validatedocsurl]: https://github.com/ulises-jeremias/v-pendulum-simulation/commits/main
+[licenseurl]: https://github.com/ulises-jeremias/v-pendulum-simulation/blob/main/LICENSE
diff --git a/examples/pendulum-simulation/animation.v b/examples/pendulum-simulation/animation.v
new file mode 100644
index 0000000000..369efd2645
--- /dev/null
+++ b/examples/pendulum-simulation/animation.v
@@ -0,0 +1,37 @@
+module main
+
+import benchmark
+import sim
+import sim.anim
+import sim.args as simargs
+
+fn main() {
+ args := simargs.parse_args(extra_workers: 1) ? as simargs.ParallelArgs
+
+ mut app := anim.new_app(args)
+ mut workers := []thread{cap: args.workers}
+
+ mut bmark := benchmark.start()
+
+ defer {
+ app.request_chan.close()
+ sim.log('Waiting for workers to finish')
+ workers.wait()
+ app.result_chan.close()
+ sim.log('Workers finished!')
+ bmark.measure(@FN)
+ sim.log('Done!')
+ }
+
+ for id in 0 .. args.workers {
+ workers << go sim.sim_worker(id, app.request_chan, [app.result_chan])
+ }
+
+ handle_request := fn [app] (request &sim.SimRequest) ? {
+ app.request_chan <- request
+ }
+
+ go app.gg.run()
+
+ sim.run(args.params, grid: args.grid, on_request: sim.SimRequestHandler(handle_request))
+}
diff --git a/examples/pendulum-simulation/full.v b/examples/pendulum-simulation/full.v
new file mode 100644
index 0000000000..301e629caf
--- /dev/null
+++ b/examples/pendulum-simulation/full.v
@@ -0,0 +1,56 @@
+module main
+
+import benchmark
+import sim
+import sim.anim
+import sim.args as simargs
+import sim.img
+
+fn main() {
+ args := simargs.parse_args(extra_workers: 2) ? as simargs.ParallelArgs
+
+ img_settings := img.image_settings_from_grid(args.grid)
+
+ mut writer := img.ppm_writer_for_fname(args.filename, img_settings) ?
+
+ mut app := anim.new_app(args)
+ mut workers := []thread{cap: args.workers + 1}
+
+ mut bmark := benchmark.start()
+
+ img_result_chan := chan &sim.SimResult{cap: args.workers}
+
+ defer {
+ image_worker := workers.pop()
+ app.request_chan.close()
+ sim.log('Waiting for workers to finish')
+ workers.wait()
+ app.result_chan.close()
+ img_result_chan.close()
+ sim.log('Waiting for image writer to finish')
+ image_worker.wait()
+ sim.log('Workers finished!')
+ bmark.measure(@FN)
+ sim.log('Closing writer file')
+ writer.close()
+ sim.log('Done!')
+ }
+
+ // start a worker on each core
+ for id in 0 .. app.args.workers {
+ workers << go sim.sim_worker(id, app.request_chan, [app.result_chan, img_result_chan])
+ }
+
+ handle_request := fn [app] (request &sim.SimRequest) ? {
+ app.request_chan <- request
+ }
+
+ workers << go img.image_worker(mut writer, img_result_chan, img_settings)
+
+ go app.gg.run()
+
+ sim.run(app.args.params,
+ grid: app.args.grid
+ on_request: sim.SimRequestHandler(handle_request)
+ )
+}
diff --git a/examples/pendulum-simulation/modules/sim/anim/app.v b/examples/pendulum-simulation/modules/sim/anim/app.v
new file mode 100644
index 0000000000..3453e7bc2e
--- /dev/null
+++ b/examples/pendulum-simulation/modules/sim/anim/app.v
@@ -0,0 +1,64 @@
+module anim
+
+import gg
+import gx
+import sim
+import sim.args as simargs
+
+const bg_color = gx.white
+
+struct Pixel {
+ x f32
+ y f32
+ color gx.Color
+}
+
+struct App {
+pub:
+ args simargs.ParallelArgs
+ request_chan chan &sim.SimRequest
+ result_chan chan &sim.SimResult
+pub mut:
+ gg &gg.Context = 0
+ iidx int
+ pixels []u32
+}
+
+pub fn new_app(args simargs.ParallelArgs) &App {
+ total_pixels := args.grid.height * args.grid.width
+
+ mut app := &App{
+ args: args
+ pixels: []u32{len: total_pixels}
+ request_chan: chan &sim.SimRequest{cap: args.grid.width}
+ }
+ app.gg = gg.new_context(
+ width: args.grid.width
+ height: args.grid.height
+ create_window: true
+ window_title: 'V Pendulum Simulation'
+ user_data: app
+ bg_color: anim.bg_color
+ frame_fn: frame
+ init_fn: init
+ )
+ return app
+}
+
+fn init(mut app App) {
+ app.iidx = app.gg.new_streaming_image(app.args.grid.width, app.args.grid.height, 4,
+ pixel_format: .rgba8)
+ go pixels_worker(mut app)
+}
+
+fn frame(mut app App) {
+ app.gg.begin()
+ app.draw()
+ app.gg.end()
+}
+
+fn (mut app App) draw() {
+ mut istream_image := app.gg.get_cached_image_by_idx(app.iidx)
+ istream_image.update_pixel_data(&app.pixels[0])
+ app.gg.draw_image(0, 0, app.args.grid.width, app.args.grid.height, istream_image)
+}
diff --git a/examples/pendulum-simulation/modules/sim/anim/worker.v b/examples/pendulum-simulation/modules/sim/anim/worker.v
new file mode 100644
index 0000000000..a3b149e024
--- /dev/null
+++ b/examples/pendulum-simulation/modules/sim/anim/worker.v
@@ -0,0 +1,19 @@
+module anim
+
+import benchmark
+import sim
+import sim.img
+
+fn pixels_worker(mut app App) {
+ mut bmark := benchmark.new_benchmark()
+ for {
+ result := <-app.result_chan or { break }
+ bmark.step()
+ // find the closest magnet
+ pixel_color := img.compute_pixel(result)
+ app.pixels[result.id] = u32(pixel_color.abgr8())
+ bmark.ok()
+ }
+ bmark.stop()
+ println(bmark.total_message(@FN))
+}
diff --git a/examples/pendulum-simulation/modules/sim/args/parser.v b/examples/pendulum-simulation/modules/sim/args/parser.v
new file mode 100644
index 0000000000..88578982e1
--- /dev/null
+++ b/examples/pendulum-simulation/modules/sim/args/parser.v
@@ -0,0 +1,158 @@
+module args
+
+import flag
+import os
+import runtime
+import sim
+import math
+
+// customisable through setting VJOBS
+const max_parallel_workers = runtime.nr_jobs()
+
+[params]
+pub struct ParserSettings {
+ sequential bool
+ img bool
+ extra_workers int
+}
+
+pub struct SequentialArgs {
+pub:
+ params sim.SimParams
+ grid sim.GridSettings
+ filename string
+}
+
+pub struct ParallelArgs {
+ SequentialArgs
+pub:
+ workers int = args.max_parallel_workers
+}
+
+pub type SimArgs = ParallelArgs | SequentialArgs
+
+pub fn parse_args(config ParserSettings) ?SimArgs {
+ if config.sequential {
+ args := parse_sequential_args() ?
+ return SimArgs(args)
+ } else {
+ args := parse_parallel_args(config.extra_workers) ?
+ return SimArgs(args)
+ }
+}
+
+fn parse_sequential_args() ?SequentialArgs {
+ mut fp := flag.new_flag_parser(os.args)
+ fp.application('vps')
+ fp.version('v0.1.0')
+ fp.limit_free_args(0, 0) ?
+ fp.description('This is a pendulum simulation written in pure V')
+ fp.skip_executable()
+
+ // output parameters
+ width := fp.int('width', `w`, sim.default_width, 'width of the image output. Defaults to $sim.default_width')
+ height := fp.int('height', `h`, sim.default_height, 'height of the image output. Defaults to $sim.default_height')
+ filename := fp.string('output', `o`, 'out.ppm', 'name of the image output. Defaults to out.ppm')
+
+ // simulation parameters
+ rope_length := fp.float('rope-length', 0, sim.default_rope_length, 'rope length to use on simulation. Defaults to $sim.default_rope_length')
+ bearing_mass := fp.float('bearing-mass', 0, sim.default_bearing_mass, 'bearing mass to use on simulation. Defaults to $sim.default_bearing_mass')
+ magnet_spacing := fp.float('magnet-spacing', 0, sim.default_magnet_spacing, 'magnet spacing to use on simulation. Defaults to $sim.default_magnet_spacing')
+ magnet_height := fp.float('magnet-height', 0, sim.default_magnet_height, 'magnet height to use on simulation. Defaults to $sim.default_magnet_height')
+ magnet_strength := fp.float('magnet-strength', 0, sim.default_magnet_strength, 'magnet strength to use on simulation. Defaults to $sim.default_magnet_strength')
+ gravity := fp.float('gravity', 0, sim.default_gravity, 'gravity to use on simulation. Defaults to $sim.default_gravity')
+
+ fp.finalize() or {
+ println(fp.usage())
+ return none
+ }
+
+ params := sim.sim_params(
+ rope_length: rope_length
+ bearing_mass: bearing_mass
+ magnet_spacing: magnet_spacing
+ magnet_height: magnet_height
+ magnet_strength: magnet_strength
+ gravity: gravity
+ )
+
+ grid := sim.new_grid_settings(
+ width: width
+ height: height
+ )
+
+ args := SequentialArgs{
+ params: params
+ filename: filename
+ grid: grid
+ }
+
+ sim.log('$args')
+
+ return args
+}
+
+fn parse_parallel_args(extra_workers int) ?ParallelArgs {
+ mut fp := flag.new_flag_parser(os.args)
+ fp.application('vps')
+ fp.version('v0.1.0')
+ fp.limit_free_args(0, 0) ?
+ fp.description('This is a pendulum simulation written in pure V')
+ fp.skip_executable()
+
+ workers := fp.int('workers', 0, args.max_parallel_workers, 'amount of workers to use on simulation. Defaults to $args.max_parallel_workers')
+
+ // output parameters
+ width := fp.int('width', `w`, sim.default_width, 'width of the image output. Defaults to $sim.default_width')
+ height := fp.int('height', `h`, sim.default_height, 'height of the image output. Defaults to $sim.default_height')
+ filename := fp.string('output', `o`, 'out.ppm', 'name of the image output. Defaults to out.ppm')
+
+ // simulation parameters
+ rope_length := fp.float('rope-length', 0, sim.default_rope_length, 'rope length to use on simulation. Defaults to $sim.default_rope_length')
+ bearing_mass := fp.float('bearing-mass', 0, sim.default_bearing_mass, 'bearing mass to use on simulation. Defaults to $sim.default_bearing_mass')
+ magnet_spacing := fp.float('magnet-spacing', 0, sim.default_magnet_spacing, 'magnet spacing to use on simulation. Defaults to $sim.default_magnet_spacing')
+ magnet_height := fp.float('magnet-height', 0, sim.default_magnet_height, 'magnet height to use on simulation. Defaults to $sim.default_magnet_height')
+ magnet_strength := fp.float('magnet-strength', 0, sim.default_magnet_strength, 'magnet strength to use on simulation. Defaults to $sim.default_magnet_strength')
+ gravity := fp.float('gravity', 0, sim.default_gravity, 'gravity to use on simulation. Defaults to $sim.default_gravity')
+
+ fp.finalize() or {
+ println(fp.usage())
+ return none
+ }
+
+ params := sim.sim_params(
+ rope_length: rope_length
+ bearing_mass: bearing_mass
+ magnet_spacing: magnet_spacing
+ magnet_height: magnet_height
+ magnet_strength: magnet_strength
+ gravity: gravity
+ )
+
+ grid := sim.new_grid_settings(
+ width: width
+ height: height
+ )
+
+ args := ParallelArgs{
+ params: params
+ filename: filename
+ grid: grid
+ workers: get_workers(workers, extra_workers)
+ }
+
+ sim.log('$args')
+
+ return args
+}
+
+[inline]
+fn get_workers(workers int, extra_workers int) int {
+ result := if workers + extra_workers <= args.max_parallel_workers {
+ workers
+ } else {
+ args.max_parallel_workers - extra_workers
+ }
+
+ return math.max(1, result)
+}
diff --git a/examples/pendulum-simulation/modules/sim/img/ppm.v b/examples/pendulum-simulation/modules/sim/img/ppm.v
new file mode 100644
index 0000000000..b7d5ba3f92
--- /dev/null
+++ b/examples/pendulum-simulation/modules/sim/img/ppm.v
@@ -0,0 +1,74 @@
+module img
+
+import gx
+import os
+import sim
+
+[params]
+pub struct ImageSettings {
+pub:
+ width int = sim.default_width
+ height int = sim.default_height
+ cache_size int = 200
+}
+
+pub fn new_image_settings(settings ImageSettings) ImageSettings {
+ return ImageSettings{
+ ...settings
+ }
+}
+
+pub fn image_settings_from_grid(grid sim.GridSettings) ImageSettings {
+ return ImageSettings{
+ width: grid.width
+ height: grid.height
+ }
+}
+
+pub fn (s ImageSettings) to_grid_settings() sim.GridSettings {
+ return sim.GridSettings{
+ width: s.width
+ height: s.height
+ }
+}
+
+pub struct PPMWriter {
+mut:
+ file os.File
+ cache []byte
+ cache_size int
+}
+
+pub fn ppm_writer_for_fname(fname string, settings ImageSettings) ?&PPMWriter {
+ mut writer := &PPMWriter{
+ cache_size: settings.cache_size
+ cache: []byte{cap: settings.cache_size}
+ }
+ writer.start_for_file(fname, settings) ?
+ return writer
+}
+
+pub fn (mut writer PPMWriter) start_for_file(fname string, settings ImageSettings) ? {
+ writer.file = os.create(fname) ?
+ writer.file.writeln('P6 $settings.width $settings.height 255') ?
+}
+
+pub fn (mut writer PPMWriter) handle_pixel(p gx.Color) ? {
+ if writer.cache.len >= writer.cache_size {
+ writer.write() ?
+ writer.flush() ?
+ }
+ writer.cache << [p.r, p.g, p.b]
+}
+
+pub fn (mut writer PPMWriter) flush() ? {
+ writer.cache.clear()
+}
+
+pub fn (mut writer PPMWriter) write() ? {
+ writer.file.write(writer.cache) ?
+}
+
+pub fn (mut writer PPMWriter) close() {
+ writer.file.close()
+}
diff --git a/examples/pendulum-simulation/modules/sim/img/worker.v b/examples/pendulum-simulation/modules/sim/img/worker.v
new file mode 100644
index 0000000000..0b335700de
--- /dev/null
+++ b/examples/pendulum-simulation/modules/sim/img/worker.v
@@ -0,0 +1,40 @@
+module img
+
+import benchmark
+import sim
+
+pub fn image_worker(mut writer PPMWriter, result_chan chan &sim.SimResult, settings ImageSettings) {
+ width := settings.width
+ height := settings.height
+ total_pixels := width * height
+
+ // as new pixels come in, write them to the image file
+ mut current_index := u64(0)
+ mut pixel_buf := []ValidColor{len: total_pixels, init: ValidColor{
+ valid: false
+ }}
+
+ mut bmark := benchmark.new_benchmark()
+ for {
+ result := <-result_chan or { break }
+
+ // find the closest magnet
+ pixel_buf[result.id].Color = compute_pixel(result)
+ pixel_buf[result.id].valid = true
+
+ for current_index < total_pixels && pixel_buf[current_index].valid {
+ bmark.step()
+ writer.handle_pixel(pixel_buf[current_index].Color) or {
+ bmark.fail()
+ sim.log(@MOD + '.' + @FN + ': pixel handler failed. Error $err')
+ break
+ }
+ bmark.ok()
+ current_index++
+ }
+ }
+ bmark.stop()
+ println(bmark.total_message(@FN))
+
+ writer.write() or { panic('Could not write image') }
+}
diff --git a/examples/pendulum-simulation/modules/sim/img/writer.v b/examples/pendulum-simulation/modules/sim/img/writer.v
new file mode 100644
index 0000000000..0e2816987c
--- /dev/null
+++ b/examples/pendulum-simulation/modules/sim/img/writer.v
@@ -0,0 +1,68 @@
+module img
+
+import gx
+import sim
+
+pub struct ValidColor {
+ gx.Color
+pub mut:
+ valid bool
+}
+
+pub struct ImageWritter {
+ settings ImageSettings
+pub mut:
+ writer PPMWriter
+ current_index int
+ buffer []ValidColor
+}
+
+pub fn new_image_writer(mut writer PPMWriter, settings ImageSettings) &ImageWritter {
+ total_pixels := settings.width * settings.height
+ mut buffer := []ValidColor{len: total_pixels, init: ValidColor{
+ valid: false
+ }}
+ return &ImageWritter{
+ writer: writer
+ settings: settings
+ buffer: buffer
+ }
+}
+
+pub fn (mut iw ImageWritter) handle(result sim.SimResult) ?int {
+ total_pixels := iw.settings.width * iw.settings.height
+
+ // find the closest magnet
+ iw.buffer[result.id].Color = compute_pixel(result)
+ iw.buffer[result.id].valid = true
+
+ for iw.current_index < total_pixels && iw.buffer[iw.current_index].valid {
+ iw.writer.handle_pixel(iw.buffer[iw.current_index].Color) or {
+ sim.log(@MOD + '.' + @FN + ': pixel handler failed. Error $err')
+ break
+ }
+ iw.current_index++
+ }
+
+ if iw.current_index == total_pixels {
+ iw.writer.write() or { panic('Could not write image') }
+ return none
+ }
+
+ return iw.current_index
+}
+
+pub fn compute_pixel(result sim.SimResult) gx.Color {
+ closest_to_m1 := result.magnet1_distance < result.magnet2_distance
+ && result.magnet1_distance < result.magnet3_distance
+ closest_to_m2 := result.magnet2_distance < result.magnet1_distance
+ && result.magnet2_distance < result.magnet3_distance
+
+ if closest_to_m1 {
+ return gx.red
+ } else if closest_to_m2 {
+ return gx.green
+ } else {
+ return gx.blue
+ }
+}
diff --git a/examples/pendulum-simulation/modules/sim/log.v b/examples/pendulum-simulation/modules/sim/log.v
new file mode 100644
index 0000000000..7f4dca5aa0
--- /dev/null
+++ b/examples/pendulum-simulation/modules/sim/log.v
@@ -0,0 +1,9 @@
+module sim
+
+// log is a helper function to print debug info
+[inline]
+pub fn log(info string) {
+ $if verbose ? {
+ println(info)
+ }
+}
diff --git a/examples/pendulum-simulation/modules/sim/params.v b/examples/pendulum-simulation/modules/sim/params.v
new file mode 100644
index 0000000000..ad19116368
--- /dev/null
+++ b/examples/pendulum-simulation/modules/sim/params.v
@@ -0,0 +1,96 @@
+module sim
+
+import math
+
+pub const (
+ default_rope_length = 0.25
+ default_bearing_mass = 0.03
+ default_magnet_spacing = 0.05
+ default_magnet_height = 0.03
+ default_magnet_strength = 10.0
+ default_gravity = 4.9
+)
+
+[params]
+pub struct SimParams {
+ rope_length f64 = sim.default_rope_length
+ bearing_mass f64 = sim.default_bearing_mass
+ magnet_spacing f64 = sim.default_magnet_spacing
+ magnet_height f64 = sim.default_magnet_height
+ magnet_strength f64 = sim.default_magnet_strength
+ gravity f64 = sim.default_gravity
+}
+
+pub fn sim_params(params SimParams) SimParams {
+ return SimParams{
+ ...params
+ }
+}
+
+pub fn (params SimParams) get_rope_vector(state SimState) Vector3D {
+ rope_origin := vector(z: params.rope_length)
+
+ return state.position + rope_origin.scale(-1)
+}
+
+pub fn (params SimParams) get_forces_sum(state SimState) Vector3D {
+ // force due to gravity
+ f_gravity := params.get_grav_force(state)
+
+ // force due to magnets
+ f_magnet1 := params.get_magnet1_force(state)
+ f_magnet2 := params.get_magnet2_force(state)
+ f_magnet3 := params.get_magnet3_force(state)
+
+ mut f_passive := vector(x: 0.0, y: 0.0, z: 0.0)
+ for force in [f_gravity, f_magnet1, f_magnet2, f_magnet3] {
+ f_passive = f_passive + force
+ }
+
+ // force due to tension of the rope
+ f_tension := params.get_tension_force(state, f_passive)
+
+ return f_passive + f_tension
+}
+
+pub fn (params SimParams) get_grav_force(state SimState) Vector3D {
+ return vector(z: -params.bearing_mass * params.gravity)
+}
+
+pub fn (params SimParams) get_magnet_position(theta f64) Vector3D {
+ return vector(
+ x: math.cos(theta) * params.magnet_spacing
+ y: math.sin(theta) * params.magnet_spacing
+ z: -params.magnet_height
+ )
+}
+
+pub fn (params SimParams) get_magnet_force(theta f64, state SimState) Vector3D {
+ magnet_position := params.get_magnet_position(theta)
+ mut diff := magnet_position + state.position.scale(-1)
+ distance_squared := diff.norm_squared()
+ diff = diff.scale(1.0 / math.sqrt(distance_squared))
+ return diff.scale(params.magnet_strength / distance_squared)
+}
+
+pub fn (params SimParams) get_magnet_dist(theta f64, state SimState) f64 {
+ return (params.get_magnet_position(theta) + state.position.scale(-1)).norm()
+}
+
+pub fn (params SimParams) get_magnet1_force(state SimState) Vector3D {
+ return params.get_magnet_force(0.0 * math.pi / 3.0, state)
+}
+
+pub fn (params SimParams) get_magnet2_force(state SimState) Vector3D {
+ return params.get_magnet_force(2.0 * math.pi / 3.0, state)
+}
+
+pub fn (params SimParams) get_magnet3_force(state SimState) Vector3D {
+ return params.get_magnet_force(4.0 * math.pi / 3.0, state)
+}
+
+pub fn (params SimParams) get_tension_force(state SimState, f_passive Vector3D) Vector3D {
+ rope_vector := params.get_rope_vector(state)
+ rope_vector_norm := rope_vector.scale(1.0 / rope_vector.norm())
+ return rope_vector_norm.scale(-1.0 * (rope_vector_norm * f_passive))
+}
diff --git a/examples/pendulum-simulation/modules/sim/params_test.v b/examples/pendulum-simulation/modules/sim/params_test.v
new file mode 100644
index 0000000000..b48ff9555d
--- /dev/null
+++ b/examples/pendulum-simulation/modules/sim/params_test.v
@@ -0,0 +1,125 @@
+module sim
+
+import math
+
+const (
+ params_test_mock_params = SimParams{
+ rope_length: 0.25
+ bearing_mass: 0.03
+ magnet_spacing: 0.05
+ magnet_height: 0.03
+ magnet_strength: 10
+ gravity: 4.9
+ }
+ params_test_mock_state = SimState{
+ position: vector(
+ x: -0.016957230930171364
+ y: -0.02937078552673521
+ z: 0.002311063475327252
+ )
+ velocity: vector(
+ x: -7.251158929833104
+ y: -12.559375680227724
+ z: -105.91539687686381
+ )
+ accel: vector(
+ x: -8.337034766251843e-11
+ y: -2.842170943040401e-10
+ z: 1.2126596023639044e-10
+ )
+ }
+ params_test_mock_tetha = 2.0 * math.pi / 3.0
+)
+
+pub fn test_get_rope_vector() {
+ result := sim.params_test_mock_params.get_rope_vector(sim.params_test_mock_state)
+ expected := vector(
+ x: -0.016957230930171364
+ y: -0.02937078552673521
+ z: -0.24768893652467275
+ )
+ assert result == expected
+}
+
+pub fn test_get_forces_sum() {
+ result := sim.params_test_mock_params.get_forces_sum(sim.params_test_mock_state)
+ expected := vector(
+ x: 3.637978807091713e-12
+ y: 5.229594535194337e-12
+ z: 9.094947017729282e-13
+ )
+ assert result == expected
+}
+
+pub fn test_get_grav_force() {
+ result := sim.params_test_mock_params.get_grav_force(sim.params_test_mock_state)
+ expected := vector(
+ z: -0.147
+ )
+ assert result == expected
+}
+
+pub fn test_get_magnet_position() {
+ result := sim.params_test_mock_params.get_magnet_position(sim.params_test_mock_tetha)
+ expected := vector(
+ x: -0.024999999999999988
+ y: 0.043301270189221946
+ z: -0.03
+ )
+ assert result == expected
+}
+
+pub fn test_get_magnet_force() {
+ result := sim.params_test_mock_params.get_magnet_force(sim.params_test_mock_tetha,
+ sim.params_test_mock_state)
+ expected := vector(
+ x: -157.45722976925555
+ y: 1422.736432604726
+ z: -632.5695169850264
+ )
+ assert result == expected
+}
+
+pub fn test_get_magnet_dist() {
+ result := sim.params_test_mock_params.get_magnet_dist(sim.params_test_mock_tetha,
+ sim.params_test_mock_state)
+ expected := 0.07993696666249227
+ assert result == expected
+}
+
+pub fn test_get_magnet1_force() {
+ result := sim.params_test_mock_params.get_magnet1_force(sim.params_test_mock_state)
+ expected := vector(
+ x: 1310.8545084099674
+ y: 575.0062553126633
+ z: -632.5695169850262
+ )
+ assert result == expected
+}
+
+pub fn test_get_magnet2_force() {
+ result := sim.params_test_mock_params.get_magnet2_force(sim.params_test_mock_state)
+ expected := vector(
+ x: -157.45722976925555
+ y: 1422.736432604726
+ z: -632.5695169850264
+ )
+ assert result == expected
+}
+
+pub fn test_get_magnet3_force() {
+ result := sim.params_test_mock_params.get_magnet3_force(sim.params_test_mock_state)
+ expected := vector(
+ x: -1710.46541088048
+ y: -2962.612996234165
+ z: -6871.632889552589
+ )
+ assert result == expected
+}
+
+pub fn test_get_tension_force() {
+ result := sim.params_test_mock_params.get_tension_force(sim.params_test_mock_state,
+ vector(x: 0.0, y: 0.0, z: 0.0))
+ expected := vector(x: 0.0, y: 0.0, z: 0.0)
+ assert result == expected
+}
diff --git a/examples/pendulum-simulation/modules/sim/runner.v b/examples/pendulum-simulation/modules/sim/runner.v
new file mode 100644
index 0000000000..bd4e92fa58
--- /dev/null
+++ b/examples/pendulum-simulation/modules/sim/runner.v
@@ -0,0 +1,96 @@
+module sim
+
+import benchmark
+import term
+
+pub type SimRequestHandler = fn (request &SimRequest) ?
+
+pub type SimStartHandler = fn () ?
+
+pub type SimFinishHandler = fn () ?
+
+pub const (
+ default_width = 600
+ default_height = 600
+)
+
+[params]
+pub struct GridSettings {
+pub:
+ width int = sim.default_width
+ height int = sim.default_height
+}
+
+pub fn new_grid_settings(settings GridSettings) GridSettings {
+ return GridSettings{
+ ...settings
+ }
+}
+
+[params]
+pub struct RunnerSettings {
+pub:
+ grid GridSettings
+ on_request SimRequestHandler
+ on_start SimStartHandler
+ on_finish SimFinishHandler
+}
+
+pub fn run(params SimParams, settings RunnerSettings) {
+ height := settings.grid.height
+ width := settings.grid.width
+
+ if !isnil(settings.on_start) {
+ settings.on_start() or {
+ log(@MOD + '.' + @FN + ': Simulation start handler failed. Error $err')
+ }
+ }
+
+ mut index := 0
+ log('')
+
+ mut bmark := benchmark.new_benchmark()
+ for y in 0 .. height {
+ $if verbose ? {
+ term.clear_previous_line()
+ }
+ log(@MOD + '.' + @FN + ': y: ${y + 1}')
+ for x in 0 .. width {
+ bmark.step()
+ // setup state conditions
+ position := vector(
+ x: 0.1 * ((f64(x) - 0.5 * f64(width - 1)) / f64(width - 1))
+ y: 0.1 * ((f64(y) - 0.5 * f64(height - 1)) / f64(height - 1))
+ z: 0.0
+ )
+ velocity := vector(x: 0, y: 0, z: 0)
+
+ mut state := new_state(
+ position: position
+ velocity: velocity
+ )
+
+ state.satisfy_rope_constraint(params)
+ request := &SimRequest{
+ id: index
+ state: state
+ params: params
+ }
+ settings.on_request(request) or {
+ log(@MOD + '.' + @FN + ': request handler failed. Error $err')
+ bmark.fail()
+ break
+ }
+ index++
+ bmark.ok()
+ }
+ }
+ bmark.stop()
+ println(bmark.total_message(@FN))
+
+ if !isnil(settings.on_finish) {
+ settings.on_finish() or {
+ log(@MOD + '.' + @FN + ': Simulation stop handler failed. Error $err')
+ }
+ }
+}
diff --git a/examples/pendulum-simulation/modules/sim/sim.v b/examples/pendulum-simulation/modules/sim/sim.v
new file mode 100644
index 0000000000..0e7fd0c2fb
--- /dev/null
+++ b/examples/pendulum-simulation/modules/sim/sim.v
@@ -0,0 +1,47 @@
+module sim
+
+pub struct SimState {
+mut:
+ position Vector3D
+ velocity Vector3D
+ accel Vector3D
+}
+
+pub fn new_state(state SimState) SimState {
+ return SimState{
+ ...state
+ }
+}
+
+pub fn (mut state SimState) satisfy_rope_constraint(params SimParams) {
+ mut rope_vector := params.get_rope_vector(state)
+ rope_vector = rope_vector.scale(params.rope_length / rope_vector.norm())
+ state.position = vector(z: params.rope_length) + rope_vector
+}
+
+pub fn (mut state SimState) increment(delta_t f64, params SimParams) {
+ // 1. add up all forces
+ // 2. get an accelleration
+ // 3. add to velocity
+ // 4. ensure rope constraint is satisfied
+
+ // sum up all forces
+ forces_sum := params.get_forces_sum(state)
+
+ // get the acceleration
+ accel := forces_sum.scale(1.0 / params.bearing_mass)
+ state.accel = accel
+
+ // update the velocity
+ state.velocity = state.velocity + accel.scale(delta_t)
+
+ // update the position
+ state.position = state.position + state.velocity.scale(delta_t)
+
+ // ensure the position satisfies rope constraint
+ state.satisfy_rope_constraint(params)
+}
+
+pub fn (state SimState) done() bool {
+ return state.velocity.norm() < 0.05 && state.accel.norm() < 0.01
+}
diff --git a/examples/pendulum-simulation/modules/sim/sim_test.v b/examples/pendulum-simulation/modules/sim/sim_test.v
new file mode 100644
index 0000000000..c27ce1e8f6
--- /dev/null
+++ b/examples/pendulum-simulation/modules/sim/sim_test.v
@@ -0,0 +1,64 @@
+module sim
+
+const (
+ sim_test_mock_params = SimParams{
+ rope_length: 0.25
+ bearing_mass: 0.03
+ magnet_spacing: 0.05
+ magnet_height: 0.03
+ magnet_strength: 10
+ gravity: 4.9
+ }
+ sim_test_mock_state = SimState{
+ position: vector(
+ x: -0.016957230930171364
+ y: -0.02937078552673521
+ z: 0.002311063475327252
+ )
+ velocity: vector(
+ x: -7.251158929833104
+ y: -12.559375680227724
+ z: -105.91539687686381
+ )
+ accel: vector(
+ x: -8.337034766251843e-11
+ y: -2.842170943040401e-10
+ z: 1.2126596023639044e-10
+ )
+ }
+)
+
+pub fn test_satisfy_rope_constraint() {
+ mut state := SimState{
+ ...sim.sim_test_mock_state
+ }
+
+ state.satisfy_rope_constraint(sim.sim_test_mock_params)
+ assert state.position.x == -0.016957230930171364
+ assert state.position.y == -0.02937078552673521
+ assert state.position.z == 0.002311063475327252
+ assert state.velocity.x == -7.251158929833104
+ assert state.velocity.y == -12.559375680227724
+ assert state.velocity.z == -105.91539687686381
+ assert state.accel.x == -8.337034766251843e-11
+ assert state.accel.y == -2.842170943040401e-10
+ assert state.accel.z == 1.2126596023639044e-10
+}
+
+pub fn test_increment() {
+ mut state := SimState{
+ ...sim.sim_test_mock_state
+ }
+
+ delta_t := 0.0005
+ state.increment(delta_t, sim.sim_test_mock_params)
+ assert state.position.x == -0.016957230930171364
+ assert state.position.y == -0.02937078552673524
+ assert state.position.z == 0.0023110634753272796
+ assert state.velocity.x == -7.251158929833044
+ assert state.velocity.y == -12.559375680227637
+ assert state.velocity.z == -105.9153968768638
+ assert state.accel.x == 1.2126596023639044e-10
+ assert state.accel.y == 1.7431981783981126e-10
+ assert state.accel.z == 3.031649005909761e-11
+}
diff --git a/examples/pendulum-simulation/modules/sim/vec.v b/examples/pendulum-simulation/modules/sim/vec.v
new file mode 100644
index 0000000000..fa00815eeb
--- /dev/null
+++ b/examples/pendulum-simulation/modules/sim/vec.v
@@ -0,0 +1,50 @@
+module sim
+
+import math
+
+// Vector3D is a 3D vector
+pub struct Vector3D {
+ x f64
+ y f64
+ z f64
+}
+
+// vector creates a Vector3D passing x,y,z as parameteres
+pub fn vector(data Vector3D) Vector3D {
+ return Vector3D{
+ ...data
+ }
+}
+
+// addition
+pub fn (v Vector3D) + (v2 Vector3D) Vector3D {
+ return Vector3D{
+ x: v.x + v2.x
+ y: v.y + v2.y
+ z: v.z + v2.z
+ }
+}
+
+// dot product
+pub fn (v Vector3D) * (v2 Vector3D) f64 {
+ return (v.x * v2.x) + (v.y * v2.y) + (v.z * v2.z)
+}
+
+// scale gets a scaled vector
+pub fn (v Vector3D) scale(scalar f64) Vector3D {
+ return Vector3D{
+ x: v.x * scalar
+ y: v.y * scalar
+ z: v.z * scalar
+ }
+}
+
+// norm_squared returns the square of the norm of the vector
+pub fn (v Vector3D) norm_squared() f64 {
+ return v * v
+}
+
+// norm returns the norm of the vector
+pub fn (v Vector3D) norm() f64 {
+ return math.sqrt(v.norm_squared())
+}
diff --git a/examples/pendulum-simulation/modules/sim/vec_test.v b/examples/pendulum-simulation/modules/sim/vec_test.v
new file mode 100644
index 0000000000..9a5a519420
--- /dev/null
+++ b/examples/pendulum-simulation/modules/sim/vec_test.v
@@ -0,0 +1,64 @@
+module sim
+
+fn test_add() {
+ v := vector(
+ x: -0.016957230930171364
+ y: -0.02937078552673521
+ z: 0.002311063475327252
+ )
+ result := v + v
+ expected := vector(
+ x: -0.03391446186034273
+ y: -0.05874157105347042
+ z: 0.004622126950654504
+ )
+ assert result == expected
+}
+
+fn test_dot() {
+ v := vector(
+ x: -0.016957230930171364
+ y: -0.02937078552673521
+ z: 0.002311063475327252
+ )
+ result := v * v
+ expected := 0.0011555317376636305
+ assert result == expected
+}
+
+fn test_scale() {
+ v := vector(
+ x: -0.016957230930171364
+ y: -0.02937078552673521
+ z: 0.002311063475327252
+ )
+ result := v.scale(2.0)
+ expected := vector(
+ x: -0.03391446186034273
+ y: -0.05874157105347042
+ z: 0.004622126950654504
+ )
+ assert result == expected
+}
+
+fn test_norm_squared() {
+ v := vector(
+ x: -0.016957230930171364
+ y: -0.02937078552673521
+ z: 0.002311063475327252
+ )
+ result := v.norm_squared()
+ expected := 0.0011555317376636305
+ assert result == expected
+}
+
+fn test_norm() {
+ v := vector(
+ x: -0.016957230930171364
+ y: -0.02937078552673521
+ z: 0.002311063475327252
+ )
+ result := v.norm()
+ expected := 0.033993113091678295
+ assert result == expected
+}
diff --git a/examples/pendulum-simulation/modules/sim/worker.v b/examples/pendulum-simulation/modules/sim/worker.v
new file mode 100644
index 0000000000..2b0e22a57e
--- /dev/null
+++ b/examples/pendulum-simulation/modules/sim/worker.v
@@ -0,0 +1,67 @@
+module sim
+
+import math
+import benchmark
+
+const (
+ max_iterations = 1000
+ simulation_delta_t = 0.0005
+)
+
+pub struct SimRequest {
+ params SimParams
+ state SimState
+pub:
+ id int
+}
+
+pub struct SimResult {
+ state SimState
+pub:
+ id int
+ magnet1_distance f64
+ magnet2_distance f64
+ magnet3_distance f64
+}
+
+pub fn sim_worker(id int, request_chan chan &SimRequest, result_channels []chan &SimResult) {
+ mut bmark := benchmark.new_benchmark()
+ for {
+ request := <-request_chan or { break }
+ bmark.step()
+ result := compute_result(request)
+ for ch in result_channels {
+ ch <- result
+ }
+ bmark.ok()
+ }
+ bmark.stop()
+ println(bmark.total_message(@FN + ': worker $id'))
+}
+
+pub fn compute_result(request SimRequest) &SimResult {
+ mut state := request.state
+ params := request.params
+
+ for _ in 0 .. sim.max_iterations {
+ state.increment(sim.simulation_delta_t, params)
+ if state.done() {
+ println('done!')
+ break
+ }
+ }
+
+ m1_dist := params.get_magnet_dist(0, state)
+ m2_dist := params.get_magnet_dist(2.0 * math.pi / 3.0, state)
+ m3_dist := params.get_magnet_dist(4.0 * math.pi / 3.0, state)
+
+ id := request.id
+
+ return &SimResult{
+ id: id
+ state: state
+ magnet1_distance: m1_dist
+ magnet2_distance: m2_dist
+ magnet3_distance: m3_dist
+ }
+}
diff --git a/examples/pendulum-simulation/modules/sim/worker_test.v b/examples/pendulum-simulation/modules/sim/worker_test.v
new file mode 100644
index 0000000000..be4bf9e1e1
--- /dev/null
+++ b/examples/pendulum-simulation/modules/sim/worker_test.v
@@ -0,0 +1,63 @@
+module sim
+
+const (
+ worker_test_mock_params = SimParams{
+ rope_length: 0.25
+ bearing_mass: 0.03
+ magnet_spacing: 0.05
+ magnet_height: 0.03
+ magnet_strength: 10
+ gravity: 4.9
+ }
+ worker_test_mock_state = SimState{
+ position: vector(
+ x: -0.016957230930171364
+ y: -0.02937078552673521
+ z: 0.002311063475327252
+ )
+ velocity: vector(
+ x: -7.251158929833104
+ y: -12.559375680227724
+ z: -105.91539687686381
+ )
+ accel: vector(
+ x: -8.337034766251843e-11
+ y: -2.842170943040401e-10
+ z: 1.2126596023639044e-10
+ )
+ }
+)
+
+fn test_compute_result() {
+ request := SimRequest{
+ id: 0
+ params: sim.worker_test_mock_params
+ state: sim.worker_test_mock_state
+ }
+ expected_state := SimState{
+ position: vector(
+ x: -0.01695723093017133
+ y: -0.02937078552673517
+ z: 0.002311063475327252
+ )
+ velocity: vector(
+ x: -7.251158929832518
+ y: -12.559375680226692
+ z: -105.91539687685668
+ )
+ accel: vector(
+ x: -3.789561257387201e-12
+ y: 3.410605131648481e-11
+ z: 3.031649005909761e-11
+ )
+ }
+ expected := &SimResult{
+ state: expected_state
+ id: 0
+ magnet1_distance: 0.07993696666249224
+ magnet2_distance: 0.07993696666249223
+ magnet3_distance: 0.03609361938278009
+ }
+ result := compute_result(request)
+ assert result == expected
+}
diff --git a/examples/pendulum-simulation/parallel.v b/examples/pendulum-simulation/parallel.v
new file mode 100644
index 0000000000..809976dadd
--- /dev/null
+++ b/examples/pendulum-simulation/parallel.v
@@ -0,0 +1,84 @@
+module main
+
+import benchmark
+import sim
+import sim.args as simargs
+import sim.img
+
+fn main() {
+ args := simargs.parse_args() ? as simargs.ParallelArgs
+
+ img_settings := img.image_settings_from_grid(args.grid)
+
+ width := img_settings.width
+ height := img_settings.height
+ total_pixels := width * height
+
+ request_chan := chan &sim.SimRequest{cap: args.workers}
+ result_chan := chan &sim.SimResult{cap: args.workers}
+
+ mut writer := img.ppm_writer_for_fname(args.filename, img_settings) ?
+ mut image_writer := img.new_image_writer(mut writer, img_settings)
+
+ mut workers := []thread{cap: args.workers}
+ mut bmark := benchmark.start()
+
+ defer {
+ request_chan.close()
+ sim.log('Waiting for workers to finish')
+ workers.wait()
+ result_chan.close()
+ bmark.measure(@FN)
+ sim.log('Closing writer file')
+ writer.close()
+ sim.log('Done!')
+ }
+
+ for id in 0 .. args.workers {
+ workers << go sim.sim_worker(id, request_chan, [result_chan])
+ }
+
+ mut x := 0
+ mut y := 0
+ mut request_index := 0
+
+ for {
+ // setup state conditions
+ position := sim.vector(
+ x: 0.1 * ((f64(x) - 0.5 * f64(width - 1)) / f64(width - 1))
+ y: 0.1 * ((f64(y) - 0.5 * f64(height - 1)) / f64(height - 1))
+ z: 0.0
+ )
+ velocity := sim.vector(x: 0, y: 0, z: 0)
+
+ mut state := sim.new_state(
+ position: position
+ velocity: velocity
+ )
+
+ state.satisfy_rope_constraint(args.params)
+ request := &sim.SimRequest{
+ id: request_index
+ state: state
+ params: args.params
+ }
+ select {
+ result := <-result_chan {
+ image_writer.handle(result) or { break }
+ }
+ else {
+ if request.id == total_pixels {
+ continue
+ }
+ request_chan <- request
+ x++
+ if x == width {
+ x = 0
+ y++
+ sim.log('y: ${y + 1}')
+ }
+ request_index++
+ }
+ }
+ }
+}
diff --git a/examples/pendulum-simulation/parallel_with_iw.v b/examples/pendulum-simulation/parallel_with_iw.v
new file mode 100644
index 0000000000..c2feed3708
--- /dev/null
+++ b/examples/pendulum-simulation/parallel_with_iw.v
@@ -0,0 +1,50 @@
+module main
+
+import benchmark
+import sim
+import sim.args as simargs
+import sim.img
+
+fn main() {
+ args := simargs.parse_args(extra_workers: 1) ? as simargs.ParallelArgs
+
+ img_settings := img.image_settings_from_grid(args.grid)
+
+ request_chan := chan &sim.SimRequest{cap: args.workers}
+ result_chan := chan &sim.SimResult{cap: args.workers}
+
+ mut writer := img.ppm_writer_for_fname(args.filename, img_settings) ?
+
+ mut workers := []thread{cap: args.workers + 1}
+ mut bmark := benchmark.start()
+
+ defer {
+ image_worker := workers.pop()
+ request_chan.close()
+ sim.log('Waiting for workers to finish')
+ workers.wait()
+ result_chan.close()
+ sim.log('Waiting for image writer to finish')
+ image_worker.wait()
+ sim.log('Workers finished!')
+ bmark.measure(@FN)
+ sim.log('Closing writer file')
+ writer.close()
+ sim.log('Done!')
+ }
+
+ for id in 0 .. args.workers {
+ workers << go sim.sim_worker(id, request_chan, [result_chan])
+ }
+
+ workers << go img.image_worker(mut writer, result_chan, img_settings)
+
+ handle_request := fn [request_chan] (request &sim.SimRequest) ? {
+ request_chan <- request
+ }
+
+ sim.run(args.params,
+ grid: args.grid
+ on_request: sim.SimRequestHandler(handle_request)
+ )
+}
diff --git a/examples/pendulum-simulation/sequential.v b/examples/pendulum-simulation/sequential.v
new file mode 100644
index 0000000000..9ec41a38d3
--- /dev/null
+++ b/examples/pendulum-simulation/sequential.v
@@ -0,0 +1,30 @@
+module main
+
+import benchmark
+import sim
+import sim.args as simargs
+import sim.img
+
+fn main() {
+ args := simargs.parse_args(sequential: true) ? as simargs.SequentialArgs
+
+ mut bmark := benchmark.start()
+ defer {
+ bmark.measure(@FN)
+ }
+
+ mut writer := img.ppm_writer_for_fname(args.filename, img.image_settings_from_grid(args.grid)) ?
+ defer {
+ writer.close()
+ }
+
+ handle_request := fn [mut writer] (request &sim.SimRequest) ? {
+ result := sim.compute_result(request)
+ pixel := img.compute_pixel(result)
+ return writer.handle_pixel(pixel)
+ }
+
+ sim.run(args.params, grid: args.grid, on_request: sim.SimRequestHandler(handle_request))
+
+ writer.write() ?
+}
diff --git a/examples/pendulum_sim/sim.v b/examples/pendulum_sim/sim.v
deleted file mode 100644
index f8ef11d0db..0000000000
--- a/examples/pendulum_sim/sim.v
+++ /dev/null
@@ -1,366 +0,0 @@
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// sim.v * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// created by: jordan bonecutter * * * * * * * * * * * * * * * * * * *
-// jpbonecutter@gmail.com * * * * * * * * * * * * * * * * * * * * * *
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-//
-// I wrote the pendulum simulator to learn V, I think it could be a
-// good addition to the examples directory.
-// Essentially, the pendulum sim runs a simulation of a pendulum with
-// a metallic tip swinging over three magnets.
-// I run this simulation with the initial position at each pixel in an
-// image and color the pixel according to the magnet over which it
-// finally rests.
-// I used some fun features in V like coroutines, channels,
-// struct embedding, mutability, methods, and the like.
-import math
-import os
-import term
-import runtime
-
-// customisable through setting VJOBS
-const parallel_workers = runtime.nr_jobs()
-
-const width = 800
-
-const height = 600
-
-struct Vec3D {
- x f64
- y f64
- z f64
-}
-
-fn (v Vec3D) add(v2 Vec3D) Vec3D {
- return Vec3D{
- x: v.x + v2.x
- y: v.y + v2.y
- z: v.z + v2.z
- }
-}
-
-fn (v Vec3D) dot(v2 Vec3D) f64 {
- return (v.x * v2.x) + (v.y * v2.y) + (v.z * v2.z)
-}
-
-fn (v Vec3D) scale(scalar f64) Vec3D {
- return Vec3D{
- x: v.x * scalar
- y: v.y * scalar
- z: v.z * scalar
- }
-}
-
-fn (v Vec3D) norm_squared() f64 {
- return v.dot(v)
-}
-
-fn (v Vec3D) norm() f64 {
- return math.sqrt(v.norm_squared())
-}
-
-struct SimState {
-mut:
- position Vec3D
- velocity Vec3D
- accel Vec3D
-}
-
-// magnets lie at [
-// math.cos(index * 2 * math.pi / 3) * magnet_spacing
-// math.sin(index * 2 * math.pi / 3) * magnet_spacing
-// -magnet_height
-// ]
-struct SimParams {
- rope_length f64
- bearing_mass f64
- magnet_spacing f64
- magnet_height f64
- magnet_strength f64
- gravity f64
-}
-
-fn (params SimParams) get_rope_vector(state SimState) Vec3D {
- rope_origin := Vec3D{
- x: 0
- y: 0
- z: params.rope_length
- }
-
- return state.position.add(rope_origin.scale(-1))
-}
-
-fn (mut state SimState) satisfy_rope_constraint(params SimParams) {
- mut rope_vector := params.get_rope_vector(state)
- rope_vector = rope_vector.scale(params.rope_length / rope_vector.norm())
- state.position = Vec3D{
- x: 0
- y: 0
- z: params.rope_length
- }.add(rope_vector)
-}
-
-fn (params SimParams) get_grav_force(state SimState) Vec3D {
- return Vec3D{
- x: 0
- y: 0
- z: -params.bearing_mass * params.gravity
- }
-}
-
-fn (params SimParams) get_magnet_position(theta f64) Vec3D {
- return Vec3D{
- x: math.cos(theta) * params.magnet_spacing
- y: math.sin(theta) * params.magnet_spacing
- z: -params.magnet_height
- }
-}
-
-fn (params SimParams) get_magnet_force(theta f64, state SimState) Vec3D {
- magnet_position := params.get_magnet_position(theta)
- mut diff := magnet_position.add(state.position.scale(-1))
- distance_squared := diff.norm_squared()
- diff = diff.scale(1.0 / math.sqrt(distance_squared))
- return diff.scale(params.magnet_strength / distance_squared)
-}
-
-fn (params SimParams) get_magnet_dist(theta f64, state SimState) f64 {
- return params.get_magnet_position(theta).add(state.position.scale(-1)).norm()
-}
-
-fn (params SimParams) get_magnet1_force(state SimState) Vec3D {
- return params.get_magnet_force(0.0 * math.pi / 3.0, state)
-}
-
-fn (params SimParams) get_magnet2_force(state SimState) Vec3D {
- return params.get_magnet_force(2.0 * math.pi / 3.0, state)
-}
-
-fn (params SimParams) get_magnet3_force(state SimState) Vec3D {
- return params.get_magnet_force(4.0 * math.pi / 3.0, state)
-}
-
-fn (params SimParams) get_tension_force(state SimState, f_passive Vec3D) Vec3D {
- rope_vector := params.get_rope_vector(state)
- rope_vector_norm := rope_vector.scale(1.0 / rope_vector.norm())
- return rope_vector_norm.scale(-1.0 * rope_vector_norm.dot(f_passive))
-}
-
-fn (mut state SimState) increment(delta_t f64, params SimParams) {
- // basically just add up all forces =>
- // get an accelleration =>
- // add to velocity =>
- // ensure rope constraint is satisfied
-
- // force due to gravity
- f_gravity := params.get_grav_force(state)
-
- // force due to each magnet
- f_magnet1 := params.get_magnet1_force(state)
-
- // force due to each magnet
- f_magnet2 := params.get_magnet2_force(state)
-
- // force due to each magnet
- f_magnet3 := params.get_magnet3_force(state)
-
- // passive forces
- f_passive := f_gravity.add(f_magnet1.add(f_magnet2.add(f_magnet3)))
-
- // force due to tension of the rope
- f_tension := params.get_tension_force(state, f_passive)
-
- // sum up all the fores
- f_sum := f_tension.add(f_passive)
-
- // get the acceleration
- accel := f_sum.scale(1.0 / params.bearing_mass)
- state.accel = accel
-
- // update the velocity
- state.velocity = state.velocity.add(accel.scale(delta_t))
-
- // update the position
- state.position = state.position.add(state.velocity.scale(delta_t))
-
- // ensure the position satisfies rope constraint
- state.satisfy_rope_constraint(params)
-}
-
-fn (state SimState) done() bool {
- return state.velocity.norm() < 0.05 && state.accel.norm() < 0.01
-}
-
-struct PPMWriter {
-mut:
- file os.File
-}
-
-struct ImageSettings {
- width int
- height int
-}
-
-struct Pixel {
- r byte
- g byte
- b byte
-}
-
-fn (mut writer PPMWriter) start_for_file(fname string, settings ImageSettings) {
- writer.file = os.create(fname) or { panic("can't create file $fname") }
- writer.file.writeln('P6 $settings.width $settings.height 255') or {}
-}
-
-fn (mut writer PPMWriter) next_pixel(p Pixel) {
- writer.file.write([p.r, p.g, p.b]) or {}
-}
-
-fn (mut writer PPMWriter) finish() {
- writer.file.close()
-}
-
-fn sim_runner(mut state SimState, params SimParams) Pixel {
- // do the simulation!
- for _ in 0 .. 1000 {
- state.increment(0.0005, params)
- if state.done() {
- println('done!')
- break
- }
- }
-
- // find the closest magnet
- m1_dist := params.get_magnet_dist(0, state)
- m2_dist := params.get_magnet_dist(2.0 * math.pi / 3.0, state)
- m3_dist := params.get_magnet_dist(4.0 * math.pi / 3.0, state)
-
- if m1_dist < m2_dist && m1_dist < m3_dist {
- return Pixel{
- r: 255
- g: 0
- b: 0
- }
- } else if m2_dist < m1_dist && m2_dist < m3_dist {
- return Pixel{
- r: 0
- g: 255
- b: 0
- }
- } else {
- return Pixel{
- r: 0
- g: 0
- b: 255
- }
- }
-}
-
-struct SimResult {
- id u64
- p Pixel
-}
-
-struct SimRequest {
- id u64
- params SimParams
-mut:
- initial SimState
-}
-
-fn sim_worker(request_chan chan SimRequest, result_chan chan SimResult) {
- // serve sim requests as they come in
- for {
- mut request := <-request_chan or { break }
-
- result_chan <- SimResult{
- id: request.id
- p: sim_runner(mut request.initial, request.params)
- }
- }
-}
-
-struct ValidPixel {
- Pixel
-mut:
- valid bool
-}
-
-fn image_worker(mut writer PPMWriter, result_chan chan SimResult, total_pixels u64) {
- // as new pixels come in, write them to the image file
- mut current_index := u64(0)
- mut pixel_buf := []ValidPixel{len: int(total_pixels), init: ValidPixel{
- valid: false
- }}
- for {
- result := <-result_chan or { break }
- pixel_buf[result.id].Pixel = result.p
- pixel_buf[result.id].valid = true
-
- for current_index < total_pixels && pixel_buf[current_index].valid {
- writer.next_pixel(pixel_buf[current_index].Pixel)
- current_index++
- }
-
- if current_index >= total_pixels {
- break
- }
- }
-}
-
-fn main() {
- params := SimParams{
- rope_length: 0.25
- bearing_mass: 0.03
- magnet_spacing: 0.05
- magnet_height: 0.03
- magnet_strength: 10.0
- gravity: 4.9
- }
-
- mut writer := PPMWriter{}
- writer.start_for_file('test.ppm', ImageSettings{
- width: width
- height: height
- })
- defer {
- writer.finish()
- }
-
- result_chan := chan SimResult{}
- request_chan := chan SimRequest{}
-
- // start a worker on each core
- for _ in 0 .. parallel_workers {
- go sim_worker(request_chan, result_chan)
- }
-
- go fn (request_chan chan SimRequest, params SimParams) {
- mut index := u64(0)
- println('')
- for y in 0 .. height {
- term.clear_previous_line()
- println('Line: $y')
- for x in 0 .. width {
- // setup initial conditions
- mut state := SimState{}
- state.position = Vec3D{
- x: 0.1 * ((f64(x) - 0.5 * f64(width - 1)) / f64(width - 1))
- y: 0.1 * ((f64(y) - 0.5 * f64(height - 1)) / f64(height - 1))
- z: 0.0
- }
- state.velocity = Vec3D{}
- state.satisfy_rope_constraint(params)
- request_chan <- SimRequest{
- id: index
- initial: state
- params: params
- }
- index++
- }
- }
- request_chan.close()
- }(request_chan, params)
-
- image_worker(mut writer, result_chan, width * height)
-}