我目前正在用Node.js编写一款小型多人游戏。
当玩家加入时,我想将他们加载到游戏世界中的请求位置。但是,这个位置可能会被占用,如果这样,我会四处寻找最近的可用空间并将其加载到该位置。
我的方法如下:
function playerJoin(){
let playerTank = new Tank();
this.findEmptyArea(playerTank, 100, 100).then((result) => {
if (!result.success) {
return;
}
addObjectToWorld(playerTank, result.x, result.y);
}
}
function findEmptyArea(object, x, y){
return new Promise((resolve, reject) => {
// Psuedo code: code iterates over game objects testing for
// collisions . if the requested position is free uses that else
// finds an empty nearby location and returns it
return resolve({success: true, x: freeX, y: freeY});
// fails to find empty location for object
return resolve({ success: false });
}
}
这不是实际的代码,而是简化的版本,以使其更加清晰。我的问题是这样:
当用户通过Web套接字连接时,playerJoin函数将运行。然后,它会创建一个新的玩家坦克,找到可用区域并返回一个诺言,如果成功,则会将玩家坦克添加到该位置的世界中。
仔细研究一下,我想知道这段代码是否有缺陷。是否有可能在实际上并非免费的位置上调用addObjectToWorld?
如下所示:
findEmptyArea承诺找到了免费的 间隔为10,10并返回promise。
.then()承诺代码块 在player1的playerJoin中(在findEmptyArea之后)运行,通过将该玩家置于10,10 addObjectToWorld
所以我想我的问题是,当一个诺言解决时,.then代码块会立即运行,而addObjectToWorld会立即运行,还是其他代码可能首先运行(例如另一个玩家也找到了空闲区域)
>谢谢您的帮助
答案 0 :(得分:0)
由于findEmptyArea
是异步的,所以很可能访问外部的东西,对吧?
Node.js是用于执行JS代码的单线程(即使最新版本中具有实验性的多线程功能)。但是Node.JS使用子线程访问外部资源。因此,只有在访问外部资源(例如文件)或执行API请求时,您才可以具有竞争条件。
如果您发出API请求,则您访问的资源将负责确保它仅返回一次true
以防止出现竞争情况。
但是,如果您仅检查Promises中的本地对象,则您不应该具有任何竞争条件(只要您不玩setTimeout
之类的东西)。 Node.JS事件循环将一次为每个已解决/已拒绝的Promise执行代码。这意味着一旦解决了Promise,then
代码块就会被执行。
我发现this article对于事件队列与Promises的组合很有帮助。
您问题的答案取决于findEmptyArea
检查空白区域的方式。
一些编码建议:
调用return
和resolve
时不需要使用reject
。您可能要使用reject
,因为您提到了变量:
this.findEmptyArea(playerTank, 100, 100).then((result) => {
addObjectToWorld(playerTank, result.x, result.y);
}).catch((err) => {
// findEmptyArea failed because the Promise was "rejected"
});
return new Promise((resolve, reject) => {
// On success call this
resolve({x: freeX, y: freeY});
// On failure call this
reject();
});
答案 1 :(得分:0)
所以我想我的问题是,当一个诺言解决时,.then代码块会立即运行,而addObjectToWorld会立即运行,还是其他代码可能首先运行(例如另一个玩家也找到了空闲区域)
>
看这个例子:
'use strict';
async function work(i) {
const interval = setInterval(() => { console.log('interval', i); }, 1);
setImmediate(() => console.log('immediate', i));
process.nextTick(() => console.log('nexttick', i));
await findEmptyArea(i);
addObjectToWorld(i, interval);
}
function addObjectToWorld(k, interval) {
console.log('add object to the world', k);
clearInterval(interval);
}
function findEmptyArea(k) {
console.log('findEmptyArea resolving', k);
return new Promise((resolve, reject) => {
console.log('findEmptyArea executing', k);
for (let i = 0; i < 1000000000; i++) {
// high computation
}
resolve({ success: true, x: 1, y: 1 });
});
}
for (let x = 0; x < 10; x++) {
work(x);
}
nextTick
在addObjectToWorld
之前运行。
nodejs事件循环是否可能允许其他东西先运行?
如果没有异步代码(I / O),请看示例,这将基于事件循环而按顺序进行。
我把它放在了承诺之内,因为随着新玩家的加入,playerJoin将经常运行,我不想阻止它。
请考虑添加Promise
或async
不会在不阻塞事件循环的情况下转换代码:您仍在使用promise阻塞事件循环(请检查{{1} }:它阻止了所有连接用户的事件循环。
为了不阻止事件循环,您应该在worker_thread或子进程中运行高级计算任务。
如果您这样做,事件循环将不会停止,因此您可以服务更多的用户并在需要时扩大规模。
但是,当然,在这种情况下,您应该实现optimistic之类的锁定模式才能预订可用空间。
示例:
for < 1000000000
// worker.js
'use strict';
const { Worker } = require('worker_threads');
async function work(i) {
const interval = setInterval(() => { console.log('interval', i); }, 1);
setImmediate(() => console.log('immediate', i));
process.nextTick(() => console.log('nexttick', i));
await findEmptyArea(i);
addObjectToWorld(i, interval);
}
function addObjectToWorld(k, interval) {
console.log('add object to the world', k);
clearInterval(interval);
}
function findEmptyArea(k) {
console.log('findEmptyArea resolving', k);
return new Promise((resolve, reject) => {
const worker = new Worker('./worker.js', { workerData: k });
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0) reject(new Error(`Worker stopped with exit code ${code}`));
});
});
}
for (let x = 0; x < 10; x++) {
work(x);
}