Weather Simulation Example


This example is a basic weather simulation playground making use of Atomic Operations, enabling multiple threads to modify a single piece of data in parallel without expensive memory copies.

This highlights an advanced use of the library which requires you to manually ensure that you do not operate on any elements you are also modifying on another thread, or you'll introduce race conditions.

When memory is shared, multiple threads can read and write the same data in memory. Atomic operations make sure that predictable values are written and read, that operations are finished before the next operation starts and that operations are not interrupted.

Learn more MDN Docs Atomics

You can play around with the different settings to see how those inpact the performance and results of the simulation, the full source code is available on the github repository page.


                    
                      // Initialize global variables
                      let grid;
                      let params; // Parameters for Hamsters.js

                      // Weather condition mapping
                      const weatherConditions = {
                        'sunny': 0,
                        'cloudy': 1,
                        'rainy': 2,
                        'stormy': 3,
                        'foggy': 4,
                        'snowy': 5
                      };

                      //Settings
                      const cols = 50;
                      const rows = 50;
                      const dampingFactor = 0.90; // Adjusted to control transition rates

                      // Define transition probabilities
                      const transitionProbs = {
                        'sunny': { cloudy: 0.10, rainy: 0.08, stormy: 0.05, foggy: 0.03, snowy: 0.02 },
                        'cloudy': { sunny: 0.07, rainy: 0.12, stormy: 0.07, foggy: 0.03, snowy: 0.02 },
                        'rainy': { sunny: 0.05, cloudy: 0.07, stormy: 0.15, foggy: 0.05, snowy: 0.03 },
                        'stormy': { sunny: 0.04, cloudy: 0.08, rainy: 0.08, foggy: 0.20, snowy: 0.04 },
                        'foggy': { sunny: 0.04, cloudy: 0.07, rainy: 0.04, stormy: 0.15, snowy: 0.12 },
                        'snowy': { sunny: 0.06, cloudy: 0.06, rainy: 0.05, stormy: 0.04, foggy: 0.12 }
                      };

                      //Hamsters.js worker body logic
                      const operator = function() {
                        const sharedArray = params.sharedArray; 
                        const cols = params.cols;
                        const rows = params.rows;
                        const weatherConditions = params.weatherConditions;
                        const dampingFactor = params.dampingFactor;
                        const transitionProbs = params.transitionProbs;

                        // Define a function to get neighbors
                        function getNeighbors(x, y) {
                            let neighbors = [];
                            for (let i = -1; i <= 1; i++) {
                                for (let j = -1; j <= 1; j++) {
                                    if (i !== 0 || j !== 0) {
                                        let ni = (x + i + cols) % cols;
                                        let nj = (y + j + rows) % rows;
                                        neighbors.push(Atomics.load(sharedArray, ni + nj * cols));
                                    }
                                }
                            }
                            return neighbors;
                        }

                        // Function to calculate the transition based on probability
                        function calculateTransition(currentState, transitions, transitionProbs, dampingFactor) {
                          for (let nextState in transitions) {
                              if (Math.random() < (transitionProbs[nextState] + Math.random() * 0.05) * dampingFactor) {
                                  return transitions[nextState];
                              }
                          }
                          return currentState;
                        }

                        // Function to find the key corresponding to a value in an object
                        function findKeyByValue(object, value) {
                          return Object.keys(object).find(key => object[key] === value);
                        }

                        // Determine the range of indices for this thread
                        const start = params.index.start || 0;
                        const end = params.index.end;

                        for (let index = start; index <= end; index++) {
                          const i = index % cols;
                          const j = Math.floor(index / cols);
                          const currentState = Atomics.load(sharedArray, index); 

                          // Get the states of neighboring cells
                          const neighbors = getNeighbors(i, j);
                          const neighborSum = neighbors.reduce((sum, n) => sum + n, 0);
                          const neighborAverage = neighborSum / neighbors.length;

                          // Find the string key corresponding to the currentState value
                          const currentStateKey = findKeyByValue(weatherConditions, currentState);

                          // Transition logic with the optimized calculateTransition function
                          const transitions = {
                              'sunny': weatherConditions['sunny'],
                              'cloudy': weatherConditions['cloudy'],
                              'rainy': weatherConditions['rainy'],
                              'stormy': weatherConditions['stormy'],
                              'foggy': weatherConditions['foggy'],
                              'snowy': weatherConditions['snowy']
                          };

                          let newState = calculateTransition(currentState, transitions, transitionProbs[currentStateKey], dampingFactor);

                          Atomics.store(sharedArray, index, newState); 
                        }
                      };

                      // Create the SharedArrayBuffer and initialize grid
                      const buffer = new SharedArrayBuffer(cols * rows * Uint8Array.BYTES_PER_ELEMENT);
                      grid = new Uint8Array(buffer);

                      // Get the selected starting weather condition from dropdown
                      const startingWeather = parseInt(document.getElementById('startingWeather').value, 10);

                      // Initialize grid with the selected starting weather condition
                      for (let i = 0; i < grid.length; i++) {
                        grid[i] = startingWeather;
                      }

                      // Get the selected number of threads from dropdown
                      const threadCount = parseInt(document.getElementById('threadCount').value, 10);

                      // Initialize params with the grid and other settings
                      const params = {
                        sharedArray: grid,
                        cols: cols,
                        rows: rows,
                        weatherConditions: weatherConditions,
                        dampingFactor: dampingFactor,
                        dataType: 'Uint8',
                        threads: threadCount, // Use selected number of threads
                        transitionProbs: transitionProbs 
                      };

                      //Process grid with Hamsters.js
                      grid = await hamsters.promise(params, operator);
                    
                

Client Summary

  • Hamsters.js Version:
  • Maximum Threads:
  • Browser:
  • Legacy Mode:
  • Atomic Operations Support:
  • Transferable Object Support:

Stats



Results