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);