From 5f3b5423af9a1ab4bfcfe0cd682537c16882641e Mon Sep 17 00:00:00 2001 From: Zap <67962871+ziap@users.noreply.github.com> Date: Mon, 22 Jan 2024 13:27:05 +0700 Subject: [PATCH] remove excessive event listener creation --- js/ai.js | 88 +++++++++++++++++++++++++---------------------- js/game.js | 33 +++++++----------- js/state.js | 3 +- js/worker.js | 3 +- src/wasm/state.c | 5 +-- wasm/ai.wasm | Bin 3142 -> 3150 bytes wasm/state.wasm | Bin 1657 -> 1688 bytes 7 files changed, 64 insertions(+), 68 deletions(-) diff --git a/js/ai.js b/js/ai.js index f6c7005..57e462e 100644 --- a/js/ai.js +++ b/js/ai.js @@ -1,64 +1,68 @@ import { state, stateBuffer } from "./state.js"; -function createWorker() { - const worker = new Worker('./js/worker.js') +function createWorkers(count) { + const workers_promise = new Array(count) + for (let i = 0; i < count; ++i) { + const worker = new Worker('./js/worker.js') - return new Promise(resolve => worker.addEventListener('message', e => { - const msg = e.data - if (msg != 'ready') throw new Error(`Expected 'ready', got ${msg}`) + workers_promise[i] = new Promise(resolve => { + worker.addEventListener('message', e => { + const msg = e.data + if (msg != 'ready') throw new Error(`Expected 'ready', got ${msg}`) - resolve(worker) - }, { once: true })) + resolve(worker) + }, { once: true }) + }) + } + + return Promise.all(workers_promise) } -function createWorkers(count) { - let workers = [createWorker()] - for (let i = 1; i < count; ++i) workers.push(createWorker()) +const workerCount = Math.max(navigator.hardwareConcurrency, 1) - return Promise.all(workers) -} +export async function registerAI(callback) { + let workers = await createWorkers(workerCount) + let working = 0 -const workerCount = navigator.hardwareConcurrency + function workerCallback(e) { + if (working == workers.length) state.set_children(e.data) + else state.add_children(e.data) -let workers = await createWorkers(workerCount) -let working = false + working-- -export async function invokeAI(strength) { - const start = performance.now() - let promises = [] + if (!working) { + state.best_move(e.data.byteLength) + callback() + } + } for (const worker of workers) { - promises.push(new Promise(resolve => { - worker.addEventListener('message', e => { - resolve(e.data) - }, { once: true }) - })) + worker.addEventListener('message', workerCallback) } - working = true - for (const worker of workers) { - const arr = new Uint8Array(stateBuffer.length) - arr.set(stateBuffer) + function invokeAI(strength) { + working = workers.length + for (const worker of workers) { + const arr = new Uint8Array(stateBuffer.length) + arr.set(stateBuffer) - const buf = arr.buffer - worker.postMessage({ strength, buf }, [buf]) + const buf = arr.buffer + worker.postMessage({ strength, buf }, [buf]) + } } - const results = await Promise.all(promises) - working = false + async function stopAI() { + if (!working) return - state.set_children(results[0]) - for (let i = 1; i < promises.length; ++i) state.add_children(promises[i]) + for (const worker of workers) worker.terminate() + workers = await createWorkers(workerCount) - state.best_move(results[0].byteLength) - console.log(`Search time: ${performance.now() - start}`) -} - -export async function stopAI() { - if (!working) return + for (const worker of workers) { + worker.addEventListener("message", workerCallback) + } - for (const worker of workers) worker.terminate() - workers = await createWorkers(workerCount) + working = 0 + } - working = false + return { invokeAI, stopAI } } diff --git a/js/game.js b/js/game.js index ec49619..8ee41d2 100644 --- a/js/game.js +++ b/js/game.js @@ -1,12 +1,12 @@ -import { invokeAI, stopAI } from './ai.js' +import { registerAI } from './ai.js' import { state } from './state.js' const gameRoot = document.querySelector('#container') const mainGrid = document.querySelector('#main-grid') -let mainCells = [] -let subCells = [] -let subGrids = [] +let mainCells = new Array(9) +let subGrids = new Array(9) +let subCells = new Array(9 * 9) for (let i = 0; i < 9; ++i) { const mainCell = document.createElement('div') @@ -23,18 +23,17 @@ for (let i = 0; i < 9; ++i) { state.move(i, j) updateHTML() - AIMove() }) subGrid.appendChild(subCell) - subCells.push(subCell) + subCells[9 * i + j] = subCell } mainCell.appendChild(subGrid) mainGrid.appendChild(mainCell) - subGrids.push(subGrid) - mainCells.push(mainCell) + subGrids[i] = subGrid + mainCells[i] = mainCell } const settingsButton = gameRoot.querySelector('#settings') @@ -44,6 +43,8 @@ const endMessage = gameRoot.querySelector('#end-message') let playerTurn = [true, false] let AIStrength = 32 +const { invokeAI, stopAI } = await registerAI(updateHTML) + function updateHTML() { for (const elem of mainCells) elem.className = 'main-cell' for (const elem of subCells) elem.className = 'sub-cell' @@ -60,7 +61,7 @@ function updateHTML() { case 0: { if (state.result) break if (state.lastMove != -1 && state.lastMove != i) break - subGrids[i].classList.add('active'); + subGrids[i].classList.add('active') } break case 1: mainCells[i].classList.add('x'); continue case 2: mainCells[i].classList.add('o'); continue @@ -78,16 +79,10 @@ function updateHTML() { } } } -} - -async function AIMove() { - if (state.result || playerTurn[state.currentPlayer]) return - - await invokeAI(AIStrength) - updateHTML() - - if (!playerTurn[state.currentPlayer]) AIMove() + if (!(state.result || playerTurn[state.currentPlayer])) { + invokeAI(AIStrength) + } } async function reset() { @@ -95,8 +90,6 @@ async function reset() { state.reset() updateHTML() - - AIMove() } restartButton.addEventListener('click', reset) diff --git a/js/state.js b/js/state.js index 3148a28..8bff5c2 100644 --- a/js/state.js +++ b/js/state.js @@ -7,8 +7,7 @@ function cstr(ptr) { let len = 0; while (mem_arr[len]) ++len - const bytes = mem_arr.slice(0, len) - return decoder.decode(bytes); + return decoder.decode(mem_arr.subarray(0, len)) } const env = { diff --git a/js/worker.js b/js/worker.js index 0abb782..f803d63 100644 --- a/js/worker.js +++ b/js/worker.js @@ -8,8 +8,7 @@ let len = 0; while (mem_arr[len]) ++len - const bytes = mem_arr.slice(0, len) - return decoder.decode(bytes); + return decoder.decode(mem_arr.subarray(0, len)) } const env = { diff --git a/src/wasm/state.c b/src/wasm/state.c index 44c48be..fd87781 100644 --- a/src/wasm/state.c +++ b/src/wasm/state.c @@ -21,10 +21,11 @@ void* children_ptr() { return children; } void combine_children(u32 children_size) { usize count = children_size / sizeof(node_t); + node_t *other = children + count; for (usize i = 0; i < count; ++i) { - children[i].value += children[i + count].value; - children[i].samples += children[i + count].samples; + children[i].value += other[i].value; + children[i].samples += other[i].samples; } } diff --git a/wasm/ai.wasm b/wasm/ai.wasm index a3a9d49da1d6ced6f31cda0ce72839aacc35b067..ec43545a22c488c7dd7090a32744900085ccf69d 100755 GIT binary patch literal 3150 zcmc&$&u<$=6rP#gwbx!daZ|fgjS#a-4i1S+0#aI9K|QIGh+g;$Q0gSx#6R-G>rK>3 zr4CUl<-nm-t;7LAFQ`=omsXtMkfI)Xqe4_d95`{|)C1=bzBjv$lc<#t5+aU0zuvrg z@0<61yR6b$QTHoM61&)XNpwKfy4aKm3(>-8_MEC=EA>_W9M7x+tz8K%L7 zVHuqDB~rZ2)zhuV%t;QKiA8^LIk+O2SzW4zjHg;*CG@9P!hnfXr8?d6E5YmnGtxgD zGrok6wt8y2+$altBvWrRC-7ScB%5qznVu@p6fFfbh0eO3QQPUrieqX!9UH9|_N<~g zwSLFy+F{T!#P-H@tl=y^hDnag@9$SXM}`3`J1!HOT_??7r2c zpUM6)!7{?ZvY;NP9!HN_v*r?#yygMn_1sjJIgZ8DC$@9%sOW1OAHB}hQ&#tk0l;mm zbsHynCN`@lwq)bq{PpbjpRe!S`-NQ_fAH%cUw!t&cbg}!o-&x)v=xJyG~KM)(!0_V zW*H97F_jC+0WY%?SW@>3`$yVV@z6`t>^ALm&FEZs+SFs=pd$rE?a&!n^p7`co>05H z##zp!*vYU%e>)BrFw51(*(q3X#V(j-H2KiYNJEX(++H-Y=H}4kYVHxEEPK`WI`RY^ zl2c#XJ4~=0$6)e^`n)?Z0N371La`~AqlC?(Fg*)7%Y-Wk^u%6QF*hNNq8N6Q(kQW_ zYZDV_hwUMv8euGGCwEQSX({pk-blShhWYvWgY(CO(Q~)FV^Tcy^0z-d|EKxwxm!pn zM-Cpy{Oy}=9>d|9E8v3HU5gy*qlC)Waq)sqN=<-Y$7JfESJ-nRlv&qK3b${11)0aE z@S&ILCx_CM=$M3g$FpZ;IVubV<(iH(ON_fIBq@-Tt#)}6-zl1A9+&p^6cO4$WFoA2 zJrqKp;xZ{ybnQojFz_tdHwl{_X#sgS0RSNsqYDhc8;LL+h`==n&jCps;2}Jq(g$#c z*tZe4IO3?bOu_2tnlhUGu1)9GaSOO2M`j443>Xw$ofQ3G16p?p*_7StdF_}8UP+Br^hJlHg6gi9lUItjEYl;y= zffmZe#xE5(Kz(V7!1l#CCjN^YwZL_RW?!xlg(s}$LDVRE16d&`-JI^q52Z|*Cnply6%|mW`LT@YBC_{VtlbTX!$Nm_4Gp;w6cM zNtHy&qp?4LV4`Wd7CfV5>rynSbpK7O_0z6H<)~A1<4q=jH(lJ}4_^mL_xlCvl|D@x9al0)-P=~f z2;GB3QoopDWE=;nr-UvNZqd=&6$< zC&y%Q(w|!nDl!_DMo(Uvefyn3Hr^Xnf_XojzT{WJ)xd9=L%oZuVP&S_m*yMGGnGbb fDA}qm&W*lxddQhsZPZKA>;914s?IN!{LA6rMD0O@ literal 3142 zcmd5;zi%8x6rP#=eR~&Q>@!%g5VMOV?8FXE5O$OZa^onrKuG)r$hp{;*th<5duuC^ zLe5x@NX-1(6U16%TOX zd-J|;mQ@<_oH52b&2ysJWak+EdGnmO+GOHIZd4amI;xkJ7UN`cYG!tBI<77-W|#&S zhGlTpX?wiM)#Hta%y$M%d%ilq7+)65oL!hr821`UC8B{36TWp=Vrt;AC^%*g%r zg7GCCz(22KZ|#FEZKOyz_h4aiw0|P868$TTFfA`uDnwgH7WDe)Scm9rw0eO7nVuc0LK0G7&mIwjXxx*m-%pRM zMClWFI7-o{QXCO3ri?+>AGR_uj$+&9fvcTFsREiJR?UV+XK0-*;8&0+IGfhTK3XiWnh*E9icjf}l#JMQ7I%o9u8yo+{lMCjRTQ5?*i&!SQ;rucZcnKmRfkj3 zUoH4NfvZI6;ButjQpNC?meTYsj2PuLA*dxS&g!Bj{p^3aoV<5v5Ap9Y1h;k9 o_G(8`i7!@@$qUs=vK&_%;)v6jou58-{KOIe{PJ8hlz#C41!Vj_Qvd(} diff --git a/wasm/state.wasm b/wasm/state.wasm index 929745c6ad047d896f3a12ddc950e5291846fc56..955a0073e8ccc208fbd28c66f7e9704159ad062f 100755 GIT binary patch delta 491 zcmYk1y-or_5XX1+PVOQSEif_BV((~9`4BY0kBTJ`V_`J$0rZfAa2^ttBhdM|B0ELfW;VamPvtX2JfAgD}-T!{gJx023FuWg}%rL^&M6d!Zgmkkgf;_}D zi7&Gv@W7CK`$|OcF!6UCDuU+;_qw_WJ|VU)XF9qlG$I7CHz18YJiDe~1Rac7s!3Dy zcj*(fzX7y1nx#vF(wu~Y2;smeqZ|&GP2kW#n?6!=XqG%9#DIT~MkCTNH2$I+M3Gxj z*8~O@#miX;83h(@Rtmo0nkrLe6OjLX!Zq7}jl@*Lnp&`?eA+b#)0o1kYbyH@5)SX- zUlojH5TD2Gw!wrZIC9(Mdm*Olc?!uXj>+&n zNcSeUxln8=xsW*-nrE{DO<%x)3fS4uGKH9rF3RHuAeq5PNHg6Yu?o_$Vp`P