mirror of https://gitee.com/cf-fz/WebCAD.git
!2439 优化:二维刀路的布尔运算在异步线程中计算
parent
06f0bc6fec
commit
7683c254e4
@ -0,0 +1,26 @@
|
||||
import { end } from "xaop";
|
||||
import { HostApplicationServices } from "../../src/ApplicationServices/HostApplicationServices";
|
||||
import { Entity } from "../../src/DatabaseServices/Entity/Entity";
|
||||
import { LoadBoardsFromFileData } from "../Utils/LoadEntity.util";
|
||||
import "../Utils/jest.util";
|
||||
|
||||
HostApplicationServices.show2DPathObject = true;
|
||||
test('二维刀路测试(常规)', async () =>
|
||||
{
|
||||
let brd = { "file": [1, "Board", 10, 2, 100, 0, 1, 3, 71, [1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 809.3041405064287, 638.3657841841923, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 809.3041405064287, 638.3657841841923, 0, 1], 0, 0, 1, 3, 800, 500, 18, true, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 2, 4, [0, 0], 0, [500, 0], 0, [500, 800], 0, [0, 800], 0, true, 0, 3, 0, 0, 0, 0, 0, 13, 2, "背板", "", "", "", "", "", 0, 0, "阿萨S", 2, 0, "0", "0", "0", "0", "", "", "", 4, "阿萨S", "阿萨S", "阿萨S", "阿萨S", true, true, 1, "1", "1", 0, 0, 0, 0, 0, 0, 0, true, 1, "Polyline", 10, 2, 0, 0, 0, 1, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 2, 3, [264.1772151898734, 924.5569620253166], 0, [264.1772151898734, 492.1518987341773], 0, [792.2784810126584, 492.1518987341773], 0, false, 0, 1, 10, 0, "4472", 3, 0, "12号刀(1)", 0, null, 1, "4472", "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -9.99999999999909, -4.547473508864641e-13, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -11939.567631031936, -1853.7579836292566, 0, 1], 0, 0, 1, 2, 31, [19.99999999999818, 2.2737367544323206e-13], 0, [0, 0], 0, [-1.500000000003638, 1.0000000000002274], 0, [-10.000000000120053, 1.0000000004658887], 0, [-10.000000000032742, 4.500000000465889], 0, [-1.500000000003638, 4.500000000465889], 0, [-3.637978807091713e-12, 5.500000000465889], 0, [-1.2005330063402653e-10, 8.499999999883812], 0, [-1.5000000001200533, 9.499999999883812], 0, [-20.000000000120053, 9.500000000611408], 0, [-20.000000000120053, 13.500000000465889], 0, [-11.499999772295268, 13.500000000611408], 0, [-9.999999772411684, 14.50000000069872], 0, [-9.999999772564479, 17.50000000046589], 0, [-11.499999772613592, 18.500000000466116], 0, [-11.499999772613592, 48.500000000466116], 0, [31.499999772609954, 48.50000000046589], 0, [31.499999772609954, 18.50000000046589], 0, [29.999999772559022, 17.50000000046589], 0, [29.999999772406227, 14.50000000069872], 0, [31.49999977228981, 13.500000000611408], 0, [40.000000000114596, 13.500000000465889], 0, [40.000000000114596, 9.500000000611408], 0, [21.500000000114596, 9.499999999883812], 0, [20.000000000114596, 8.499999999883812], 0, [19.99999999999818, 5.500000000465889], 0, [21.49999999999818, 4.500000000465889], 0, [30.000000000027285, 4.500000000465889], 0, [30.000000000114596, 1.0000000004658887], 0, [21.49999999999818, 1.0000000000002274], 0, [19.99999999999818, 2.2737367544323206e-13], 0, false, 0], "basePt": { "x": 809.3041405064287, "y": 620.3657841841923, "z": 0 }, "ucs": [1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1] };
|
||||
|
||||
let br = LoadBoardsFromFileData(brd)[0];
|
||||
|
||||
br.MeshGeometry;//预读取
|
||||
|
||||
await new Promise<void>((res, rej) =>
|
||||
{
|
||||
end((new Entity()).AsyncUpdated, function ()
|
||||
{
|
||||
res();
|
||||
});
|
||||
});
|
||||
|
||||
expect(br.MeshGeometry.getAttribute("position").count).toMatchNumberSnapshot(0);
|
||||
expect(br.EdgeGeometry.getAttribute("position").count).toMatchNumberSnapshot(0);
|
||||
});
|
@ -0,0 +1,5 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`二维刀路测试(常规) 1`] = `"1125"`;
|
||||
|
||||
exports[`二维刀路测试(常规) 2`] = `"1118"`;
|
@ -0,0 +1,8 @@
|
||||
|
||||
export interface Task
|
||||
{
|
||||
key: any; //关键key
|
||||
data: any;
|
||||
then: Function;
|
||||
workerCtor: (new () => Worker);
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
import { Task } from "./Task";
|
||||
|
||||
/** 是否输出测试信息 */
|
||||
const isLog = false;
|
||||
/** 调试输出 */
|
||||
const log = isLog ? console.log.bind(console) : () => { };
|
||||
|
||||
interface Thread
|
||||
{
|
||||
id: number;
|
||||
task: Task;
|
||||
worker: Worker;
|
||||
workerCtor: (new () => Worker);
|
||||
}
|
||||
|
||||
/** 池化模型
|
||||
*/
|
||||
class WorkerPool
|
||||
{
|
||||
/** 线程池 */
|
||||
private _Pool: Thread[] = [];
|
||||
/** 调度队列 */
|
||||
private _TaskQueue: Task[] = [];
|
||||
/** 最大线程数 */
|
||||
THREAD_MAX_COUNT = 4;
|
||||
/** 线程ID */
|
||||
private threadId = 0;
|
||||
|
||||
/** 添加任务 */
|
||||
AppendTask(task: Task)
|
||||
{
|
||||
const thread = this._Pool.find(thread => thread.task?.key === task.key && thread.workerCtor === task.workerCtor);
|
||||
if (thread)
|
||||
{
|
||||
thread.worker.terminate();
|
||||
thread.worker = new task.workerCtor;
|
||||
this.UpdateTask(thread, task);
|
||||
log(`重启线程任务`);
|
||||
return;
|
||||
}
|
||||
|
||||
const taskIndex = this._TaskQueue.findIndex(e => e.key === task.key && e.workerCtor === task.workerCtor);
|
||||
if (taskIndex !== -1)
|
||||
{
|
||||
this._TaskQueue[taskIndex] = task;
|
||||
}
|
||||
else
|
||||
{
|
||||
this._TaskQueue.push(task);
|
||||
this.Loop();
|
||||
}
|
||||
}
|
||||
|
||||
TerminateTask(task: Pick<Task, "key" | "workerCtor">)
|
||||
{
|
||||
const thread = this._Pool.find(thread => thread.task?.key === task.key && thread.workerCtor === task.workerCtor);
|
||||
|
||||
let index = this._TaskQueue.findIndex(t => t.key === task.key && t.workerCtor === task.workerCtor);
|
||||
if (index !== -1)
|
||||
this._TaskQueue.splice(index, 1);
|
||||
|
||||
if (thread)
|
||||
{
|
||||
thread.worker.terminate();
|
||||
thread.worker = undefined;
|
||||
thread.workerCtor = undefined;
|
||||
thread.task = undefined;
|
||||
|
||||
this.Loop();
|
||||
}
|
||||
}
|
||||
|
||||
/** 更换任务 */
|
||||
UpdateTask(thread: Thread, task: Task)
|
||||
{
|
||||
thread.task = task;
|
||||
|
||||
if (thread.workerCtor !== task.workerCtor)
|
||||
{
|
||||
if (thread.worker) thread.worker.terminate();
|
||||
|
||||
thread.worker = new task.workerCtor;
|
||||
thread.workerCtor = task.workerCtor;
|
||||
}
|
||||
|
||||
log(`设置【${thread.id}号线程】执行【任务${task.key}】`);
|
||||
thread.worker.postMessage(thread.task.data);
|
||||
// 返回结果
|
||||
thread.worker.onmessage = e =>
|
||||
{
|
||||
const res = {
|
||||
id: thread.id,
|
||||
taskId: task.key,
|
||||
data: e.data
|
||||
};
|
||||
log(`【${res.id}号线程】完成【任务${res.taskId}】`, res.data);
|
||||
thread.task = null;
|
||||
this.Loop();//先开启下一个任务 在进行主线程同步
|
||||
task.then(res);
|
||||
};
|
||||
}
|
||||
|
||||
/** 循环调度 */
|
||||
Loop()
|
||||
{
|
||||
if (!this._TaskQueue.length)
|
||||
{
|
||||
//没有任务时,我们杀掉worker 避免占据内存
|
||||
for (let thread of this._Pool)
|
||||
{
|
||||
if (thread.task) continue;
|
||||
|
||||
if (thread.worker)
|
||||
thread.worker.terminate();
|
||||
thread.worker = undefined;
|
||||
thread.workerCtor = undefined;
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
const freeThread = this.GetFreeThread();
|
||||
if (freeThread)
|
||||
{
|
||||
const task = this._TaskQueue.shift();
|
||||
this.UpdateTask(freeThread, task);
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取闲置线程 */
|
||||
GetFreeThread(): Thread | undefined
|
||||
{
|
||||
let thread = this._Pool.find(thread => !thread.task);
|
||||
if (thread) return thread;
|
||||
|
||||
if (this._Pool.length < this.THREAD_MAX_COUNT)
|
||||
{
|
||||
thread = {
|
||||
id: ++this.threadId,
|
||||
worker: undefined,
|
||||
task: undefined,
|
||||
workerCtor: undefined
|
||||
};
|
||||
this._Pool.push(thread);
|
||||
return thread;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 线程池
|
||||
* @example
|
||||
* const task = {
|
||||
* data: 100, // 输入的数据
|
||||
* then: e => console.log(e.data) // 输出的结果
|
||||
* };
|
||||
* // 添加任务
|
||||
* THREAD_POOL.AppendTask(task);
|
||||
*/
|
||||
export const WORKER_POOL = new WorkerPool();
|
@ -0,0 +1,82 @@
|
||||
import { measureArea } from '@jscad/modeling/src/geometries/poly3';
|
||||
import { subtract, union } from '@jscad/modeling/src/operations/booleans';
|
||||
import { Max } from '../../Nest/Common/Util';
|
||||
import { FuzzyFactory } from '../../csg/core/FuzzyFactory';
|
||||
|
||||
//并集path2dCsgs 差集 删除小面积结果
|
||||
export function CSGSubtract(geom: geom3.Geom3[], path2dCsgs: geom3.Geom3[])
|
||||
{
|
||||
let gemUnion = path2dCsgs[0];
|
||||
for (let i = 1; i < path2dCsgs.length; i++)
|
||||
gemUnion = union(gemUnion, path2dCsgs[i]);
|
||||
let newGeom = subtract(geom, gemUnion);
|
||||
|
||||
//删除小面积(只留一个)
|
||||
{
|
||||
let fuzz = new FuzzyFactory;
|
||||
let vmap = new Map;
|
||||
for (let poly of newGeom.polygons)
|
||||
{
|
||||
for (let v of poly.vertices)
|
||||
{
|
||||
let key = fuzz.lookupOrCreate(v, v);
|
||||
let arr = vmap.get(key);
|
||||
if (!arr)
|
||||
{
|
||||
arr = [];
|
||||
vmap.set(key, arr);
|
||||
}
|
||||
arr.push(poly);
|
||||
|
||||
v["__key__"] = key;
|
||||
}
|
||||
}
|
||||
|
||||
let polys = newGeom.polygons.concat();
|
||||
let polyGroups = [];
|
||||
|
||||
let calcs = new Set;
|
||||
while (polys.length)
|
||||
{
|
||||
let poly1 = polys.pop();
|
||||
calcs.add(poly1);
|
||||
let polyGroup = [poly1];
|
||||
polyGroups.push(polyGroup);
|
||||
|
||||
for (let i = 0; i < polyGroup.length; i++)
|
||||
{
|
||||
let poly = polyGroup[i];
|
||||
|
||||
for (let v of poly.vertices)
|
||||
{
|
||||
let key = v["__key__"];
|
||||
let arr = vmap.get(key);
|
||||
|
||||
for (let vpoly of arr)
|
||||
{
|
||||
if (calcs.has(vpoly)) continue;
|
||||
|
||||
calcs.add(vpoly);
|
||||
|
||||
polyGroup.push(vpoly);
|
||||
}
|
||||
}
|
||||
}
|
||||
// arrayRemoveIf(polys, poly => !calcs.has(poly)); //加上这个无法提高性能
|
||||
}
|
||||
|
||||
let areas = polyGroups.map(polys =>
|
||||
{
|
||||
let area = 0;
|
||||
for (let poly of polys)
|
||||
area += measureArea(poly);
|
||||
return area;
|
||||
});
|
||||
|
||||
let maxIndex = Max(areas, (t1, t2) => t2 > t1);
|
||||
|
||||
newGeom.polygons = polyGroups[maxIndex];
|
||||
}
|
||||
|
||||
return newGeom;
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/* __JEST_WEBWORKER_SEPARATOR__ */
|
||||
|
||||
import { CSGSubtract } from './CSGSubtract';
|
||||
|
||||
const ctx: Worker = self as any;
|
||||
|
||||
ctx.addEventListener("message", e =>
|
||||
{
|
||||
const [path2dCsgs, geom]: geom3.Geom3[][] = e.data;
|
||||
try
|
||||
{
|
||||
const newGeom = CSGSubtract(geom, path2dCsgs);
|
||||
postMessage({
|
||||
status: 0,
|
||||
geom: newGeom
|
||||
});
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
postMessage({
|
||||
status: 1,
|
||||
error
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default {} as typeof Worker & (new () => Worker);
|
@ -0,0 +1,20 @@
|
||||
import { Task } from "../../Common/ThreadPool/Task";
|
||||
|
||||
export type CSGTask = Pick<Task, "key" | "data" | "then">;
|
||||
|
||||
type CSGSubtractFunction = (task: CSGTask) => void;
|
||||
export const _CSGSubtractInjectInteractionFunctions: CSGSubtractFunction[] = [];
|
||||
export function AddCSGSubtractTask(task: CSGTask)
|
||||
{
|
||||
for (let f of _CSGSubtractInjectInteractionFunctions)
|
||||
f(task);
|
||||
}
|
||||
|
||||
export type TaskKey = Pick<Task, "key">;
|
||||
type TerminateCSGTaskFunction = (task: TaskKey) => void;
|
||||
export const _TerminateTaskInjectInteractionFunctions: TerminateCSGTaskFunction[] = [];
|
||||
export function TerminateCSGTask(task: TaskKey)
|
||||
{
|
||||
for (let f of _TerminateTaskInjectInteractionFunctions)
|
||||
f(task);
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import { CSGSubtract } from "./CSGSubtract";
|
||||
import { CSGTask, _CSGSubtractInjectInteractionFunctions, _TerminateTaskInjectInteractionFunctions } from "./CSGSubtractTaskManager";
|
||||
|
||||
|
||||
//因为jest不支持测试 所以这里使用了异步模式,模仿和webworker一样的结果
|
||||
|
||||
export function RegisterCSGSyncMode()
|
||||
{
|
||||
_CSGSubtractInjectInteractionFunctions.push(async (task: CSGTask) =>
|
||||
{
|
||||
let [path2dCsgs, geom] = task.data;
|
||||
await undefined;//保证它是异步的
|
||||
try
|
||||
{
|
||||
const newGeom = CSGSubtract(geom, path2dCsgs);
|
||||
task.then({
|
||||
data: {
|
||||
status: 0,
|
||||
geom: newGeom
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
task.then({
|
||||
data: {
|
||||
status: 1,
|
||||
error
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_TerminateTaskInjectInteractionFunctions.push((task) =>
|
||||
{
|
||||
});
|
||||
}
|
||||
|
||||
RegisterCSGSyncMode();
|
@ -0,0 +1,23 @@
|
||||
import { Task } from "../../Common/ThreadPool/Task";
|
||||
import { WORKER_POOL } from "../../Common/ThreadPool/WorkerPool";
|
||||
import CSGWorker from "../../Geometry/CSGSubtract/CSGSubtract.worker";
|
||||
import { CSGTask, _CSGSubtractInjectInteractionFunctions, _TerminateTaskInjectInteractionFunctions } from "./CSGSubtractTaskManager";
|
||||
|
||||
export function RegisterCSGWebWorker()
|
||||
{
|
||||
_CSGSubtractInjectInteractionFunctions.push((task: CSGTask) =>
|
||||
{
|
||||
let workerTask = task as Task;
|
||||
workerTask.workerCtor = CSGWorker;
|
||||
WORKER_POOL.AppendTask(workerTask);
|
||||
});
|
||||
|
||||
_TerminateTaskInjectInteractionFunctions.push((task) =>
|
||||
{
|
||||
let workerTask = task as Task;
|
||||
workerTask.workerCtor = CSGWorker;
|
||||
WORKER_POOL.TerminateTask(workerTask);
|
||||
});
|
||||
|
||||
WORKER_POOL.THREAD_MAX_COUNT = navigator.hardwareConcurrency / 2;
|
||||
}
|
Loading…
Reference in new issue