框架代码同步

This commit is contained in:
fan 2023-08-02 16:13:51 +08:00
parent c4ed2bde5d
commit 5a6848b005
7 changed files with 264 additions and 174 deletions

@ -1 +1 @@
Subproject commit 3a87487697fdde8d8d361fe784f90a2a08915387
Subproject commit 643ec9bfaf328f2b4b4de5f5203a3230bc1a0efb

View File

@ -533,24 +533,17 @@ export class GraphicApp extends EventEmitter<GraphicAppEvents> {
*/
handleGraphicStates(graphicStates: GraphicState[]) {
graphicStates.forEach((state) => {
const list = this.queryStore.queryByIdOrCodeAndType(
const g = this.queryStore.queryByCodeAndType(
state.code,
state.graphicType
);
if (list.length == 0) {
if (!g) {
const template = this.getGraphicTemplatesByType(state.graphicType);
const g = template.new();
g.loadState(state);
this.addGraphics(g);
} else {
// 调整逻辑:所有图形对象状态数据更新完后再统一重绘
list
.filter((g) => {
return g.updateStates(state);
})
.forEach((g) => {
g.repaint();
});
} else if (g.updateStates(state)) {
g.repaint();
}
});
}

View File

@ -1,29 +1,37 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import EventEmitter from 'eventemitter3';
import { IMessageClient } from './MessageBroker';
export type MessageHandler = (data: any) => void;
import { GraphicApp } from '../app';
import { CompleteMessageCliOption, IMessageClient } from './MessageBroker';
export interface MessageClientEvents {
connecting: [ctx: any];
connected: [ctx: any];
disconnected: [ctx: any];
}
/**
*
*/
export interface ISubscription {
export type HandleMessage = (data: any) => void;
export interface IMessageHandler {
/**
*
* id
*/
unsubscribe(): void;
get App(): GraphicApp;
/**
*
* @param data
*/
handle(data: any): void;
}
export abstract class MessageClient
extends EventEmitter<MessageClientEvents>
implements IMessageClient
{
options: CompleteMessageCliOption;
subClients: SubscriptionClient[] = []; // 订阅客户端
constructor(options: CompleteMessageCliOption) {
super();
this.options = options;
}
/**
*
* @param destination
@ -31,10 +39,118 @@ export abstract class MessageClient
*/
abstract subscribe(
destination: string,
handle: MessageHandler
): ISubscription;
handle: HandleMessage
): IUnsubscriptor;
unsubscribe(destination: string): void {
this.unsubscribe0(destination);
const idx = this.subClients.findIndex(
(cli) => cli.destination === destination
);
if (idx >= 0) {
this.subClients.splice(idx, 1);
}
}
abstract unsubscribe0(destination: string): void;
getOrNewSubClient(destination: string): SubscriptionClient {
let cli = this.subClients.find((cli) => cli.destination === destination);
if (!cli) {
// 不存在,新建
cli = new SubscriptionClient(this, destination, this.options.protocol);
this.subClients.push(cli);
}
return cli;
}
addSubscription(destination: string, handler: IMessageHandler): void {
const cli = this.getOrNewSubClient(destination);
cli.addHandler(handler);
}
removeSubscription(destination: string, handle: IMessageHandler): void {
this.getOrNewSubClient(destination).removeHandler(handle);
}
abstract get connected(): boolean;
abstract close(): void;
}
/**
*
*/
export interface IUnsubscriptor {
/**
*
*/
unsubscribe(): void;
}
export class SubscriptionClient {
mc: MessageClient;
destination: string;
protocol: 'json' | 'protobuf';
handlers: IMessageHandler[] = [];
unsubscriptor?: IUnsubscriptor;
constructor(
mc: MessageClient,
destination: string,
protocal: 'json' | 'protobuf'
) {
this.mc = mc;
this.destination = destination;
this.protocol = protocal;
this.mc.on('disconnected', this.onDisconnect, this);
this.mc.on('connected', this.trySubscribe, this);
}
addHandler(handler: IMessageHandler) {
if (this.handlers.filter((h) => h.App === handler.App).length == 0) {
this.handlers.push(handler);
}
if (!this.unsubscriptor) {
this.trySubscribe();
}
}
removeHandler(handler: IMessageHandler) {
const idx = this.handlers.findIndex((h) => h.App === handler.App);
if (idx >= 0) {
this.handlers.splice(idx, 1);
}
if (this.handlers.length == 0) {
console.log(`订阅${this.destination}没有消息监听处理,移除订阅`);
this.unsubscribe();
}
}
trySubscribe(): void {
if (this.mc.connected) {
this.unsubscriptor = this.mc.subscribe(this.destination, (data) => {
this.handleMessage(data);
});
}
}
unsubscribe(): void {
this.mc.unsubscribe(this.destination);
}
handleMessage(data: any) {
if (this.protocol === 'json') {
console.debug('收到消息:', data);
}
this.handlers.forEach((handler) => {
try {
handler.handle(data);
} catch (error) {
console.error('图形应用状态消息处理异常', error);
}
});
}
onDisconnect() {
this.unsubscriptor = undefined;
}
}

View File

@ -1,17 +1,17 @@
import { State } from 'centrifuge';
import { State, Subscription } from 'centrifuge';
import Centrifuge from 'centrifuge/build/protobuf';
import { MessageCliOption } from './MessageBroker';
import {
ISubscription,
HandleMessage,
IUnsubscriptor,
MessageClient,
MessageHandler,
} from './BasicMessageClient';
import { CompleteMessageCliOption } from './MessageBroker';
export class CentrifugeMessagingClient extends MessageClient {
options: MessageCliOption;
cli: Centrifuge;
constructor(options: MessageCliOption) {
super();
constructor(options: CompleteMessageCliOption) {
super(options);
this.options = options;
if (this.options.protocol === 'json') {
this.cli = new Centrifuge(options.wsUrl, {
@ -28,7 +28,6 @@ export class CentrifugeMessagingClient extends MessageClient {
this.cli
.on('connecting', (ctx) => {
console.debug(`centrifuge连接中: ${ctx}, ${ctx.reason}`);
this.emit('connecting', ctx);
})
.on('connected', (ctx) => {
console.debug(`连接成功: ${ctx.transport}`);
@ -48,26 +47,23 @@ export class CentrifugeMessagingClient extends MessageClient {
return this.cli.state === State.Connected;
}
subscribe(destination: string, handle: MessageHandler): ISubscription {
const sub = this.cli.newSubscription(destination);
sub
.on('publication', (ctx) => {
if (this.options.protocol === 'json') {
console.log('收到centrifuge消息:', ctx.data);
}
try {
handle(ctx.data);
} catch (error) {
console.log('websocket状态消息处理异常', error);
}
})
.on('subscribed', (ctx) => {
console.log('订阅centrifuge服务消息成功', destination, ctx);
})
.subscribe();
subscribe(destination: string, handle: HandleMessage): IUnsubscriptor {
let sub = this.cli.getSubscription(destination);
if (!sub) {
sub = this.cli.newSubscription(destination);
sub.on('publication', (ctx) => {
handle(ctx.data);
});
}
sub.subscribe();
return sub;
}
unsubscribe0(destination: string): void {
const subClient = this.getOrNewSubClient(destination);
this.cli.removeSubscription(subClient.unsubscriptor as Subscription);
}
close(): void {
this.cli.disconnect();
}

View File

@ -1,13 +1,14 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import EventEmitter from 'eventemitter3';
import { GraphicApp } from '../app';
import { GraphicState } from '../core';
import {
IMessageHandler,
IUnsubscriptor,
MessageClientEvents,
} from './BasicMessageClient';
import { CentrifugeMessagingClient } from './CentrifugeBroker';
import { StompMessagingClient } from './WsMsgBroker';
import { GraphicState } from '../core';
import { GraphicApp } from '../app';
import {
ISubscription,
MessageClientEvents,
MessageHandler,
} from './BasicMessageClient';
export enum ClientEngine {
Stomp,
@ -31,46 +32,32 @@ export interface MessageCliOption {
* token
*/
token?: string;
// /**
// * 认证失败处理
// * @returns
// */
// onAuthenticationFailed?: () => void;
// /**
// * 连接成功处理
// * @param ctx
// * @returns
// */
// onConnected?: (ctx: unknown) => void;
// /**
// * 端口连接处理
// */
// onDisconnected?: (ctx: unknown) => void;
// // 重连延时默认3秒,设置为0不重连.
// reconnectDelay?: number;
// // 服务端过来的心跳间隔默认30秒
// heartbeatIncoming?: number;
// // 到服务端的心跳间隔默认30秒
// heartbeatOutgoing?: number;
}
const DefaultStompOption: MessageCliOption = {
export interface CompleteMessageCliOption extends MessageCliOption {
protocol: 'json' | 'protobuf';
}
const DefaultStompOption: CompleteMessageCliOption = {
engine: ClientEngine.Stomp,
protocol: 'protobuf',
wsUrl: '',
token: '',
// reconnectDelay: 3000,
// heartbeatIncoming: 30000,
// heartbeatOutgoing: 30000,
};
export interface IMessageClient extends EventEmitter<MessageClientEvents> {
/**
*
*
* @param destination
* @param handle
* @param handler
*/
subscribe(destination: string, handle: MessageHandler): ISubscription;
addSubscription(destination: string, handler: IMessageHandler): void;
/**
*
* @param destination
* @param handler
*/
removeSubscription(destination: string, handler: IMessageHandler): void;
/**
*
@ -85,7 +72,7 @@ export interface IMessageClient extends EventEmitter<MessageClientEvents> {
export class WsMsgCli {
private static client: IMessageClient;
private static options: MessageCliOption;
private static options: CompleteMessageCliOption;
private static appMsgBroker: AppWsMsgBroker[] = [];
static new(options: MessageCliOption) {
if (WsMsgCli.client) {
@ -97,25 +84,10 @@ export class WsMsgCli {
WsMsgCli.client = new CentrifugeMessagingClient(WsMsgCli.options);
} else {
WsMsgCli.client = new StompMessagingClient(WsMsgCli.options);
// WsMsgCli.client.onStompError = (frame: Frame) => {
// const errMsg = frame.headers['message'];
// if (errMsg === '401') {
// console.warn('认证失败,断开WebSocket连接');
// StompCli.close();
// if (StompCli.options.onAuthenticationFailed) {
// StompCli.options.onAuthenticationFailed();
// }
// } else {
// console.error('收到Stomp错误消息', frame);
// }
// };
}
const cli = WsMsgCli.client;
cli.on('connected', () => {
WsMsgCli.emitConnectStateChangeEvent(true);
WsMsgCli.appMsgBroker.forEach((broker) => {
broker.resubscribe();
});
});
cli.on('disconnected', () => {
WsMsgCli.emitConnectStateChangeEvent(false);
@ -132,14 +104,12 @@ export class WsMsgCli {
return WsMsgCli.client && WsMsgCli.client.connected;
}
static trySubscribe(
destination: string,
handler: MessageHandler
): ISubscription | undefined {
if (WsMsgCli.isConnected()) {
return WsMsgCli.client.subscribe(destination, handler);
}
return undefined;
static registerSubscription(destination: string, handler: IMessageHandler) {
WsMsgCli.client.addSubscription(destination, handler);
}
static unregisterSubscription(destination: string, handler: IMessageHandler) {
WsMsgCli.client.removeSubscription(destination, handler);
}
static registerAppMsgBroker(broker: AppWsMsgBroker) {
@ -193,7 +163,31 @@ export interface AppStateSubscription {
*
* 使
*/
subscription?: ISubscription;
subscription?: IUnsubscriptor;
}
class AppMessageHandler implements IMessageHandler {
app: GraphicApp;
sub: AppStateSubscription;
constructor(app: GraphicApp, subOptions: AppStateSubscription) {
this.app = app;
if (!subOptions.messageConverter && !subOptions.messageHandle) {
throw new Error(`没有消息处理器或图形状态消息转换器: ${subOptions}`);
}
this.sub = subOptions;
}
get App(): GraphicApp {
return this.app;
}
handle(data: any): void {
const sub = this.sub;
if (sub.messageConverter) {
const graphicStates = sub.messageConverter(data);
this.app.handleGraphicStates(graphicStates);
} else if (sub.messageHandle) {
sub.messageHandle(data);
}
}
}
/**
@ -201,9 +195,9 @@ export interface AppStateSubscription {
*/
export class AppWsMsgBroker {
app: GraphicApp;
subscriptions: Map<string, AppStateSubscription> = new Map<
subscriptions: Map<string, AppMessageHandler> = new Map<
string,
AppStateSubscription
AppMessageHandler
>();
constructor(app: GraphicApp) {
@ -212,44 +206,21 @@ export class AppWsMsgBroker {
}
subscribe(sub: AppStateSubscription) {
this.unsbuscribe(sub.destination); // 先尝试取消之前订阅
sub.subscription = WsMsgCli.trySubscribe(sub.destination, (data) => {
if (sub.messageConverter) {
const graphicStates = sub.messageConverter(data);
this.app.handleGraphicStates(graphicStates);
} else if (sub.messageHandle) {
sub.messageHandle(data);
} else {
console.error(
`订阅destination:${sub.destination}没有消息处理器或图形状态消息转换器`
);
}
});
this.subscriptions.set(sub.destination, sub);
}
/**
*
*/
resubscribe() {
this.subscriptions.forEach((record) => {
this.subscribe(record);
});
const handler = new AppMessageHandler(this.app, sub);
WsMsgCli.registerSubscription(sub.destination, handler);
this.subscriptions.set(sub.destination, handler);
}
unsbuscribe(destination: string) {
const oldSub = this.subscriptions.get(destination);
if (oldSub) {
if (oldSub.subscription && WsMsgCli.isConnected()) {
oldSub.subscription.unsubscribe();
}
oldSub.subscription = undefined;
WsMsgCli.unregisterSubscription(destination, oldSub);
}
}
unsbuscribeAll() {
this.subscriptions.forEach((record) => {
this.unsbuscribe(record.destination);
this.subscriptions.forEach((record, destination) => {
this.unsbuscribe(destination);
});
}

View File

@ -1,20 +1,20 @@
import { Client as StompClient, type Frame } from '@stomp/stompjs';
import { MessageCliOption } from './MessageBroker';
import {
ISubscription,
HandleMessage,
IUnsubscriptor,
MessageClient,
MessageHandler,
} from './BasicMessageClient';
import { CompleteMessageCliOption } from './MessageBroker';
const ReconnectDelay = 3000;
const HeartbeatIncoming = 30000;
const HeartbeatOutgoing = 30000;
export class StompMessagingClient extends MessageClient {
options: MessageCliOption;
cli: StompClient;
constructor(options: MessageCliOption) {
super();
constructor(options: CompleteMessageCliOption) {
super(options);
this.options = options;
this.cli = new StompClient({
brokerURL: options.wsUrl,
@ -27,6 +27,9 @@ export class StompMessagingClient extends MessageClient {
});
this.cli.onConnect = () => {
// this.subClients.forEach((cli) => {
// this.subscribe(cli.destination, cli.handleMessage);
// });
this.emit('connected', '');
};
this.cli.onStompError = (frame: Frame) => {
@ -55,7 +58,7 @@ export class StompMessagingClient extends MessageClient {
return this.cli.connected;
}
subscribe(destination: string, handle: MessageHandler): ISubscription {
subscribe(destination: string, handle: HandleMessage): IUnsubscriptor {
const sub = this.cli.subscribe(
destination,
(frame) => {
@ -73,6 +76,10 @@ export class StompMessagingClient extends MessageClient {
return sub;
}
unsubscribe0(destination: string): void {
this.cli.unsubscribe(destination);
}
close(): void {
this.cli.deactivate();
}

View File

@ -695,36 +695,43 @@ export function splitLineEvenly(
});
}
/** 计算折线的平行线 */
export function getParallelOfPolyline(
points: IPointData[],
direction: 'L' | 'R',
offset: number
) {
if (points.length < 2) throw Error('折线点不能少于2个');
const normalVecs = points.map((p, i) => {
if (points[i - 1] && points[i + 1]) {
offset: number,
side: 'L' | 'R'
): IPointData[] {
const { PI, cos, acos } = Math;
const angleBase = side === 'L' ? -PI / 2 : PI / 2;
return points.map((p, i) => {
let baseUnitVec: Vector2; //上一段的基准单位向量
let angle: number; //偏转角度
let len: number; //结合偏转角度的实际偏移量
if (!points[i - 1] || !points[i + 1]) {
angle = angleBase;
len = offset;
baseUnitVec = points[i - 1]
? new Vector2([
p.x - points[i - 1].x,
p.y - points[i - 1].y,
]).normalize()
: new Vector2([
points[i + 1].x - p.x,
points[i + 1].y - p.y,
]).normalize();
} else {
let point;
if (points[i + 1]) {
point = new Vector2([
points[i + 1].x - p.x,
points[i + 1].y - p.y,
]).normalize();
} else {
point = new Vector2([
p.x - points[i - 1].x,
p.y - points[i - 1].y,
]).normalize();
}
const rotate = new Matrix().rotate(
(direction === 'L' ? Math.PI : -Math.PI) / 2
);
return rotate.apply(point);
const vp = new Vector2([p.x - points[i - 1].x, p.y - points[i - 1].y]);
const vn = new Vector2([points[i + 1].x - p.x, points[i + 1].y - p.y]);
const cosTheta = Vector2.dot(vn, vp) / (vp.length() * vn.length());
const direction = vp.x * vn.y - vp.y * vn.x > 0; //det(vp|vn)>0?
const theta = direction ? acos(cosTheta) : -acos(cosTheta);
angle = angleBase + theta / 2;
len = offset / cos(theta / 2);
baseUnitVec = Vector2.from(vp).normalize();
}
return new Matrix()
.scale(len, len)
.rotate(angle)
.translate(p.x, p.y)
.apply(baseUnitVec);
});
return points.map((p, i) => ({
x: p.x + offset * normalVecs[i]?.x,
y: p.y + offset * normalVecs[i]?.y,
}));
}