Compare commits
5 Commits
4651ebc5c8
...
6df6dbcaf6
Author | SHA1 | Date | |
---|---|---|---|
6df6dbcaf6 | |||
a6f5d0062a | |||
e22e27fd69 | |||
d805e4dfae | |||
f6aa131caa |
@ -57,6 +57,7 @@ async function main() {
|
||||
await waitEvent(bot, 'inject_allowed');
|
||||
bot.loadPlugin((await import('mineflayer-event-promise')).default);
|
||||
bot.loadPlugin((await import('mineflayer-control')).default);
|
||||
bot.loadPlugin((await import('mineflayer-fly-control')).default);
|
||||
await bot.waitEvent('spawn');
|
||||
|
||||
async function loadReplContextModules(context) {
|
||||
@ -72,12 +73,14 @@ async function main() {
|
||||
return bot.players[args.owner];
|
||||
};
|
||||
|
||||
context.debug = debug;
|
||||
context.sc = {};
|
||||
context.sc.pos = () => bot.entity.position;
|
||||
context.sc.debug_enable = (module) => debug.enable(module);
|
||||
context.sc.debug_disable = (module) => debug.disable(module);
|
||||
context.sc.debug_mfc = () => debug.enable('mineflayer-control');
|
||||
context.sc.debug_mff = () => debug.enable('mineflayer-fly-control');
|
||||
context.sc.q = () => bot.quit();
|
||||
context.sc.sleep = asyncSleep;
|
||||
context.sc.tossHeld = () => bot.tossStack(bot.heldItem);
|
||||
}
|
||||
|
||||
if (!args.noRepl) {
|
||||
@ -89,6 +92,8 @@ async function main() {
|
||||
terminal: true,
|
||||
ignoreUndefined: true,
|
||||
});
|
||||
|
||||
r.on('exit', () => bot.quit());
|
||||
loadReplContextModules(r.context);
|
||||
}
|
||||
}
|
||||
|
@ -14,8 +14,7 @@
|
||||
"mineflayer-event-promise": "file:plugin/event-promise",
|
||||
"compass-utils": "file:utils",
|
||||
"mineflayer-control": "file:plugin/control",
|
||||
"mineflayer-pathfinder": "^2.4.5",
|
||||
"prismarine-viewer": "^1.25.0",
|
||||
"mineflayer-fly-control": "file:plugin/fly-control",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import debug from 'debug';
|
||||
import { Queue, Task, isIterable } from 'compass-utils';
|
||||
import { Vec3 } from 'vec3';
|
||||
import assert from 'node:assert/strict';
|
||||
const logger = debug('mineflayer-control');
|
||||
|
||||
// yaw = axis * Math.PI / 2
|
||||
@ -27,10 +28,12 @@ export const AXIS_UNIT = {
|
||||
3: new Vec3(1, 0, 0),
|
||||
};
|
||||
|
||||
AXIS_UNIT['-Z'] = AXIS_UNIT['NORTH'] = AXIS_UNIT[0];
|
||||
AXIS_UNIT['-X'] = AXIS_UNIT['WEST'] = AXIS_UNIT[1];
|
||||
AXIS_UNIT['+Z'] = AXIS_UNIT['SOUTH'] = AXIS_UNIT[2];
|
||||
AXIS_UNIT['+X'] = AXIS_UNIT['EAST'] = AXIS_UNIT[3];
|
||||
export const AXIS_NAME = {
|
||||
0: '-Z',
|
||||
1: '-X',
|
||||
2: '+Z',
|
||||
3: '+X',
|
||||
};
|
||||
|
||||
export const MOVE_LEVEL = {
|
||||
WALK: 1,
|
||||
@ -94,8 +97,16 @@ export class MovePathBlockedError extends Error {
|
||||
constructor() { super('Move path is possiblely blocked.'); }
|
||||
};
|
||||
|
||||
export class NotOnGroundError extends Error {
|
||||
constructor() { super('bot is not on ground, cannot jump'); }
|
||||
};
|
||||
|
||||
async function moveAxisTask(bot, task, axis_raw, target_raw, level) {
|
||||
const axis = AXIS[axis_raw];
|
||||
assert.equal(typeof axis, 'number', 'axis');
|
||||
assert.ok(0 <= axis && axis <= 3, 'axis');
|
||||
assert.equal(typeof level, 'number', 'level');
|
||||
assert.ok(target_raw instanceof Vec3, 'target');
|
||||
const stable_axis = "xz"[axis % 2];
|
||||
const target = target_raw.clone();
|
||||
adjustXZ(target);
|
||||
@ -138,7 +149,7 @@ async function moveAxisTask(bot, task, axis_raw, target_raw, level) {
|
||||
let time_used = 0, pos_queue = new Queue();
|
||||
const TRACK_TICKS = 5;
|
||||
pos_queue.push(pos.clone());
|
||||
do {
|
||||
while (true) {
|
||||
await bot.waitForTicks(1);
|
||||
task._interuptableHere();
|
||||
|
||||
@ -166,7 +177,7 @@ async function moveAxisTask(bot, task, axis_raw, target_raw, level) {
|
||||
let pos5t = pos_queue.front();
|
||||
if (pos.distanceSquared(pos5t) < Number.EPSILON) {
|
||||
logger('moveAxisTask() position changed too little.');
|
||||
logger(`moveAxisTask() position 5 ticks ago: ${pos5t}.`);
|
||||
logger(`moveAxisTask() position ${TRACK_TICKS} ticks ago: ${pos5t}.`);
|
||||
logger(`moveAxisTask() position now: ${pos}.`);
|
||||
throw new MovePathBlockedError();
|
||||
}
|
||||
@ -175,7 +186,7 @@ async function moveAxisTask(bot, task, axis_raw, target_raw, level) {
|
||||
delta.update(target.minus(pos));
|
||||
remaining_dis = delta.dot(AXIS_UNIT[axis]);
|
||||
if (Math.abs(remaining_dis) <= 0.5) {
|
||||
logger('moveAxisTask() very close to target now.');
|
||||
logger(`moveAxisTask() very close! remain: ${remaining_dis}.`);
|
||||
pos.x = target.x;
|
||||
pos.z = target.z;
|
||||
bot.entity.velocity.x = 0;
|
||||
@ -187,11 +198,63 @@ async function moveAxisTask(bot, task, axis_raw, target_raw, level) {
|
||||
logger('moveAxisTask() went past target.');
|
||||
throw new MoveInterferedError();
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
bot.clearControlStates();
|
||||
task._ready(time_used);
|
||||
}
|
||||
|
||||
async function ladderAscendTask(bot, task, target_y) {
|
||||
assert.equal(typeof target_y, 'number', 'target_y');
|
||||
bot.control.adjustXZ();
|
||||
const start_pos = bot.entity.position.clone();
|
||||
logger(`ladderAscendTask() initial position: ${start_pos}.`);
|
||||
logger(`ladderAscendTask() target y: ${target_y}.`);
|
||||
|
||||
if (start_pos.y > target_y) {
|
||||
throw new Error('Invalid Argument: target_y is smaller than current y.');
|
||||
}
|
||||
|
||||
let controls = new ControlState('jump');
|
||||
controls.apply(bot);
|
||||
logger('ladderAscendTask() started.');
|
||||
|
||||
const TRACK_TICKS = 10;
|
||||
let time_used = 0, last_y = start_pos.y;
|
||||
while (true) {
|
||||
await bot.waitForTicks(1);
|
||||
task._interuptableHere();
|
||||
time_used += 1;
|
||||
|
||||
const pos = bot.entity.position;
|
||||
if (pos.xzDistanceTo(start_pos) > 1) { throw new MoveInterferedError(); }
|
||||
bot.control.adjustXZ();
|
||||
|
||||
if (Math.abs(pos.y - target_y) < 0.2) {
|
||||
logger('ladderAscendTask() reached.');
|
||||
bot.clearControlStates();
|
||||
pos.y = target_y;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pos.y - target_y > 0.4) {
|
||||
logger('ladderAscendTask() went past target.');
|
||||
throw new MoveInterferedError();
|
||||
}
|
||||
|
||||
if (time_used % TRACK_TICKS == 0) {
|
||||
let now_y = pos.y;
|
||||
if (Math.abs(now_y - last_y) < 0.3) {
|
||||
logger(`ladderAscendTask() move too little in past ${TRACK_TICKS} ticks!`);
|
||||
logger(`ladderAscendTask() now pos.y=${now_y}`);
|
||||
logger(`ladderAscendTask() ${TRACK_TICKS} ticks ago pos.y=${last_y}`);
|
||||
throw new MovePathBlockedError();
|
||||
}
|
||||
last_y = now_y;
|
||||
}
|
||||
}
|
||||
task._ready(time_used);
|
||||
}
|
||||
|
||||
export default function inject(bot) {
|
||||
bot.control = {};
|
||||
bot.control.getState = () => { return ControlState.from(bot.controlState); };
|
||||
@ -213,6 +276,12 @@ export default function inject(bot) {
|
||||
|
||||
bot.control.jumpUp = async (axis_raw, time=5) => {
|
||||
const axis = AXIS[axis_raw];
|
||||
assert.ok(typeof axis == 'number');
|
||||
assert.ok(0 <= axis && axis <= 3);
|
||||
assert.ok(typeof time == 'number');
|
||||
if (!bot.entity.onGround) {
|
||||
throw new NotOnGroundError();
|
||||
}
|
||||
bot.control.adjustXZ();
|
||||
await bot.look(axis * Math.PI / 2, 0, true);
|
||||
let controls = new ControlState('forward', 'jump');
|
||||
@ -230,6 +299,14 @@ export default function inject(bot) {
|
||||
if (tactic == null) { tactic = {}; }
|
||||
if (tactic.sprint == null) { tactic.sprint = dis > 3; }
|
||||
if (tactic.speed == null) { tactic.speed = tactic.sprint ? .355 : .216; }
|
||||
assert.ok(typeof axis == 'number');
|
||||
assert.ok(0 <= axis && axis <= 3);
|
||||
assert.ok(typeof dis == 'number');
|
||||
assert.ok(typeof tactic.sprint == 'boolean');
|
||||
assert.ok(typeof tactic.speed == 'number');
|
||||
if (!bot.entity.onGround) {
|
||||
throw new NotOnGroundError();
|
||||
}
|
||||
|
||||
const axis = AXIS[axis_raw];
|
||||
bot.control.adjustXZ();
|
||||
@ -264,8 +341,61 @@ export default function inject(bot) {
|
||||
}
|
||||
|
||||
bot.control.jump = async () => {
|
||||
if (!bot.entity.onGround) {
|
||||
throw new NotOnGroundError();
|
||||
}
|
||||
bot.setControlState('jump', true);
|
||||
await bot.waitForTicks(1);
|
||||
bot.setControlState('jump', false);
|
||||
};
|
||||
|
||||
bot.control.jumpToHighest = () => {
|
||||
let task = new Task();
|
||||
queueMicrotask(async () => {
|
||||
try {
|
||||
task._start();
|
||||
|
||||
if (!bot.entity.onGround) {
|
||||
throw new NotOnGroundError();
|
||||
}
|
||||
|
||||
let controls = new ControlState('jump');
|
||||
controls.apply(bot);
|
||||
|
||||
let time_used = 0;
|
||||
while (true) {
|
||||
await bot.waitForTicks(1);
|
||||
task._interuptableHere();
|
||||
|
||||
time_used += 1;
|
||||
if (time_used == 1) {
|
||||
controls.jump = false;
|
||||
controls.apply(bot);
|
||||
}
|
||||
if (bot.entity.velocity.y < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
task._ready(time_used);
|
||||
} catch(err) {
|
||||
bot.clearControlStates();
|
||||
task._fail(err);
|
||||
}
|
||||
});
|
||||
return task;
|
||||
};
|
||||
|
||||
bot.control.ladderAscend = (target_y) => {
|
||||
let task = new Task();
|
||||
queueMicrotask(async () => {
|
||||
try {
|
||||
task._start();
|
||||
await ladderAscendTask(bot, task, target_y);
|
||||
} catch(err) {
|
||||
bot.clearControlStates();
|
||||
task._fail(err);
|
||||
}
|
||||
});
|
||||
return task;
|
||||
};
|
||||
}
|
||||
|
146
plugin/fly-control/index.mjs
Normal file
146
plugin/fly-control/index.mjs
Normal file
@ -0,0 +1,146 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import debug from 'debug';
|
||||
import { Task } from 'compass-utils';
|
||||
const logger = debug('mineflayer-fly-control');
|
||||
|
||||
export class ElytraNotEquippedError extends Error {
|
||||
constructor() { super('Elytra is not equipped!'); }
|
||||
}
|
||||
|
||||
export class InsufficientRocketError extends Error {
|
||||
constructor(flight_requirement, flight_actual) {
|
||||
super(`Expected ${flight_requirement} flight in total, got ${flight_actual}`);
|
||||
this.flight_requirement = flight_requirement;
|
||||
this.flight_actual = flight_actual;
|
||||
}
|
||||
};
|
||||
|
||||
export class AlreadyElytraFlyingError extends Error {
|
||||
constructor() { super('Already elytra flying!'); }
|
||||
}
|
||||
|
||||
export function fireworkFlight(firework_item) {
|
||||
return firework_item?.nbt?.value?.Fireworks?.value?.Flight?.value ?? 1;
|
||||
}
|
||||
|
||||
async function takeOffTask(bot, task) {
|
||||
if (bot.entity.elytraFlying) { throw new AlreadyElytraFlyingError(); }
|
||||
if (bot.entity.onGround) {
|
||||
await task._waitDependent(bot.control.jumpToHighest());
|
||||
task._interuptableHere();
|
||||
}
|
||||
|
||||
await bot.elytraFly();
|
||||
task._interuptableHere();
|
||||
}
|
||||
|
||||
async function ascendTask(bot, task, flight, gracefulMode) {
|
||||
assert.ok(typeof flight == 'number');
|
||||
bot.control.adjustXZ();
|
||||
await bot.look(0, Math.PI / 2, true);
|
||||
task._interuptableHere();
|
||||
|
||||
if (!bot.entity.elytraFlying) { await takeOffTask(bot, task); }
|
||||
await bot.waitForTicks(1);
|
||||
task._interuptableHere();
|
||||
|
||||
function gracefulModePredicate() {
|
||||
return bot.entity.velocity.y >= 0.01;
|
||||
}
|
||||
|
||||
function fastModePredicate() {
|
||||
return bot.fireworkRocketDuration > 0;
|
||||
}
|
||||
|
||||
const predicate = gracefulMode ? gracefulModePredicate : fastModePredicate;
|
||||
|
||||
let flight_pre_rocket = fireworkFlight(bot.heldItem);
|
||||
for (let i = 0; i < flight; i += flight_pre_rocket) {
|
||||
bot.activateItem();
|
||||
do {
|
||||
await bot.waitForTicks(Math.max(1, bot.fireworkRocketDuration));
|
||||
task._interuptableHere();
|
||||
} while (predicate());
|
||||
}
|
||||
}
|
||||
|
||||
export default function inject(bot) {
|
||||
const firework_id = bot.registry.itemsByName.firework_rocket.id;
|
||||
const elytra_id = bot.registry.itemsByName.elytra.id;
|
||||
const elytra_slot = bot.getEquipmentDestSlot('torso');
|
||||
|
||||
bot.flyctl = {};
|
||||
bot.flyctl.skipValidation = false;
|
||||
function beforeFlyValidation(flight_requirement = 0) {
|
||||
assert.ok(typeof flight_requirement == 'number');
|
||||
assert.ok(flight_requirement >= 0);
|
||||
|
||||
if (bot.flyctl.skipValidation) {
|
||||
logger('beforeFlyValidation() skipped.');
|
||||
return;
|
||||
}
|
||||
|
||||
logger(`beforeFlyValidation() elytra slot: ${elytra_slot}`);
|
||||
let elytra_slot_item = bot.inventory.slots[elytra_slot]?.type;
|
||||
if (elytra_slot_item != elytra_id) {
|
||||
logger(`beforeFlyValidation() failed: elytra slot found ${elytra_slot_item}.`);
|
||||
logger(`beforeFlyValidation() expected ${elytra_id}.`);
|
||||
throw new ElytraNotEquippedError();
|
||||
}
|
||||
|
||||
if (flight_requirement > 0) {
|
||||
let rocket_item = bot.heldItem;
|
||||
if (rocket_item?.type != firework_id) {
|
||||
logger('beforeFlyValidation() failed: holding is not rocket.');
|
||||
logger(`beforeFlyValidation() found ${rocket_item?.type} expected ${firework_id} .`);
|
||||
throw new InsufficientRocketError(flight_requirement, 0);
|
||||
}
|
||||
|
||||
let flight_sum = rocket_item.count * fireworkFlight(rocket_item);
|
||||
if (flight_sum < flight_requirement) {
|
||||
throw new InsufficientRocketError(flight_requirement, flight_sum);
|
||||
}
|
||||
}
|
||||
|
||||
logger('beforeFlyValidation() passed.');
|
||||
}
|
||||
|
||||
bot.flyctl.prepare = async () => {
|
||||
await bot.equip(elytra_id, 'torso');
|
||||
await bot.equip(firework_id);
|
||||
};
|
||||
|
||||
bot.flyctl.ascend = async (flight = 1, gracefulMode = true) => {
|
||||
let task = new Task();
|
||||
queueMicrotask(async () => {
|
||||
try {
|
||||
beforeFlyValidation(flight);
|
||||
task._ready(await ascendTask(bot, task, flight, gracefulMode));
|
||||
} catch(err) {
|
||||
task._fail(err);
|
||||
}
|
||||
});
|
||||
return task;
|
||||
};
|
||||
|
||||
bot.flyctl.gracefulAscend = (flight = 1) => {
|
||||
return bot.flyctl.ascend(flight, true);
|
||||
};
|
||||
|
||||
bot.flyctl.fastAscend = (flight = 1) => {
|
||||
return bot.flyctl.ascend(flight, false);
|
||||
};
|
||||
|
||||
bot.flyctl.takeOff = () => {
|
||||
let task = new Task();
|
||||
queueMicrotask(async () => {
|
||||
try {
|
||||
beforeFlyValidation(flight);
|
||||
task._ready(await takeOffTask(bot, task));
|
||||
} catch(err) {
|
||||
task._fail(err);
|
||||
}
|
||||
});
|
||||
return task;
|
||||
};
|
||||
}
|
11
plugin/fly-control/package.json
Normal file
11
plugin/fly-control/package.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "mineflayer-fly-control",
|
||||
"description": "High-level & handy API for mineflayer bot elytra flying.",
|
||||
"type": "module",
|
||||
"main": "index.mjs",
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
"vec3": "^0.1.8",
|
||||
"compass-utils": "file:../../utils"
|
||||
}
|
||||
}
|
@ -1,63 +1,6 @@
|
||||
import fs from 'node:fs/promises';
|
||||
|
||||
// Reads JSON data from file, returns null if file not found or has other errors.
|
||||
export async function readJsonFile(path) {
|
||||
try {
|
||||
const data = await fs.readFile(path, 'utf8');
|
||||
return JSON.parse(data);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Write JSON data into a file.
|
||||
export function writeJsonFile(path, data) {
|
||||
const json_string = JSON.stringify(data);
|
||||
return fs.writeFile(path, json_string, 'utf8');
|
||||
}
|
||||
|
||||
// Parse format "profile@host:port", port can be undefined.
|
||||
export function parseLogin(url) {
|
||||
const [profile_host, port] = url.split(':');
|
||||
const [profile, host] = profile_host.split('@');
|
||||
return [profile, host, port ? parseInt(port) : undefined];
|
||||
}
|
||||
|
||||
// Returns a promise, wait unitl the EventEmitter emits certian event next time.
|
||||
export function waitEvent(em, event) {
|
||||
return new Promise((resolve, _reject) => {
|
||||
em.once(event, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
export function asyncSleep(t) {
|
||||
return new Promise((resolve, _reject) => {
|
||||
setTimeout(resolve, t);
|
||||
});
|
||||
}
|
||||
|
||||
export function asyncTimeout(t) {
|
||||
return new Promise((_resolve, reject) => {
|
||||
setTimeout(reject, t);
|
||||
});
|
||||
}
|
||||
|
||||
export function promiseTimeout(p, t) {
|
||||
return Promise.race([p, asyncTimeout(t)]);
|
||||
}
|
||||
|
||||
export function yieldTask() {
|
||||
return new Promise((resolve, _reject) => {
|
||||
queueMicrotask(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
// Checks wheather an object is iterable.
|
||||
export function isIterable(obj) {
|
||||
if (obj == null) { return false; }
|
||||
return typeof obj[Symbol.iterator] == 'function';
|
||||
}
|
||||
|
||||
export { readJsonFile, writeJsonFile, parseLogin, waitEvent,
|
||||
asyncSleep, asyncTimeout, promiseTimeout, yieldTask,
|
||||
isIterable} from './misc.mjs';
|
||||
export { Queue, QueueEmptyError } from './queue.mjs';
|
||||
export { AsyncLock } from './async-lock.mjs';
|
||||
export { Task, TaskInteruptedError } from './task.mjs';
|
||||
|
59
utils/misc.mjs
Normal file
59
utils/misc.mjs
Normal file
@ -0,0 +1,59 @@
|
||||
import fs from 'node:fs/promises';
|
||||
|
||||
// Reads JSON data from file, returns null if file not found or has other errors.
|
||||
export async function readJsonFile(path) {
|
||||
try {
|
||||
const data = await fs.readFile(path, 'utf8');
|
||||
return JSON.parse(data);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Write JSON data into a file.
|
||||
export function writeJsonFile(path, data) {
|
||||
const json_string = JSON.stringify(data);
|
||||
return fs.writeFile(path, json_string, 'utf8');
|
||||
}
|
||||
|
||||
// Parse format "profile@host:port", port can be undefined.
|
||||
export function parseLogin(url) {
|
||||
const [profile_host, port] = url.split(':');
|
||||
const [profile, host] = profile_host.split('@');
|
||||
return [profile, host, port ? parseInt(port) : undefined];
|
||||
}
|
||||
|
||||
// Returns a promise, wait unitl the EventEmitter emits certian event next time.
|
||||
export function waitEvent(em, event) {
|
||||
return new Promise((resolve, _reject) => {
|
||||
em.once(event, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
export function asyncSleep(t) {
|
||||
return new Promise((resolve, _reject) => {
|
||||
setTimeout(resolve, t);
|
||||
});
|
||||
}
|
||||
|
||||
export function asyncTimeout(t) {
|
||||
return new Promise((_resolve, reject) => {
|
||||
setTimeout(reject, t);
|
||||
});
|
||||
}
|
||||
|
||||
export function promiseTimeout(p, t) {
|
||||
return Promise.race([p, asyncTimeout(t)]);
|
||||
}
|
||||
|
||||
export function yieldTask() {
|
||||
return new Promise((resolve, _reject) => {
|
||||
queueMicrotask(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
// Checks wheather an object is iterable.
|
||||
export function isIterable(obj) {
|
||||
if (obj == null) { return false; }
|
||||
return typeof obj[Symbol.iterator] == 'function';
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
export class TaskInteruptedError extends Error {
|
||||
constructor() { super('Task has been interupted.'); }
|
||||
};
|
||||
@ -8,6 +10,7 @@ export class Task {
|
||||
#promise;
|
||||
#resolve;
|
||||
#reject;
|
||||
#dependent_tasks
|
||||
|
||||
constructor() {
|
||||
this.id = ++task_id;
|
||||
@ -18,10 +21,15 @@ export class Task {
|
||||
this.#resolve = resolve;
|
||||
this.#reject = reject;
|
||||
});
|
||||
this.#dependent_tasks = null;
|
||||
}
|
||||
|
||||
isDone() {
|
||||
return this.#promise == null;
|
||||
}
|
||||
|
||||
_ready(result) {
|
||||
if (this.#promise == null) { return; }
|
||||
if (this.isDone()) { return; }
|
||||
this.result = result;
|
||||
this.status = Task.STATUS.ready;
|
||||
this.#resolve(this.result);
|
||||
@ -29,7 +37,7 @@ export class Task {
|
||||
}
|
||||
|
||||
_fail(error) {
|
||||
if (this.#promise == null) { return; }
|
||||
if (this.isDone()) { return; }
|
||||
this.error = error;
|
||||
this.status = Task.STATUS.failed;
|
||||
this.#reject(this.error);
|
||||
@ -38,11 +46,16 @@ export class Task {
|
||||
|
||||
_start() {
|
||||
if (this.status != Task.STATUS.pending) {
|
||||
throw new Error('Task has already left pending stage');
|
||||
throw new Error('_start() called twice');
|
||||
}
|
||||
this.status = Task.STATUS.running;
|
||||
}
|
||||
|
||||
finally() {
|
||||
if (this.isDone()) { return Promise.resolve(); }
|
||||
return this.#promise.finally();
|
||||
}
|
||||
|
||||
interupt() {
|
||||
if (this.status == Task.STATUS.pending) {
|
||||
this._confirmInterupt();
|
||||
@ -51,14 +64,32 @@ export class Task {
|
||||
|
||||
if (this.#promise == null) { return Promise.resolve(); }
|
||||
this.status = Task.STATUS.interupting;
|
||||
if (this.#dependent_tasks) {
|
||||
for (let dependent of this.#dependent_tasks) {
|
||||
dependent.interupt();
|
||||
}
|
||||
}
|
||||
return this.#promise.finally();
|
||||
}
|
||||
|
||||
async _waitDependent(dependent) {
|
||||
assert.equal(this.#dependent_tasks, null);
|
||||
if (dependent instanceof Task) { this.#dependent_tasks = [dependent]; }
|
||||
else { this.#dependent_tasks = dependent; }
|
||||
await Promise.allSettled(this.#dependent_tasks.map(t => t.finally()));
|
||||
this.#dependent_tasks = null;
|
||||
if (dependent instanceof Task) {
|
||||
return await dependent.get();
|
||||
}
|
||||
return dependent.map(async t => await t.get());
|
||||
}
|
||||
|
||||
_shouldInterupt() {
|
||||
return this.status == Task.STATUS.interupting;
|
||||
}
|
||||
|
||||
_confirmInterupt() {
|
||||
assert.equal(this.#dependent_tasks, null);
|
||||
this.status = Task.STATUS.interupted;
|
||||
this.error = new TaskInteruptedError();
|
||||
this.#reject(this.error);
|
||||
|
Loading…
x
Reference in New Issue
Block a user