This commit is contained in:
IDONTSUDO 2025-01-19 21:37:27 +03:00
parent c2da1d8f4c
commit c2b91ee4d7
23 changed files with 432 additions and 137 deletions

View file

@ -20,6 +20,7 @@
"GLTF", "GLTF",
"grau", "grau",
"idontsudo", "idontsudo",
"Pids",
"raycaster", "raycaster",
"skils", "skils",
"typedataset", "typedataset",

View file

@ -15,12 +15,12 @@
"checkCommand": null, "checkCommand": null,
"filter": null "filter": null
}, },
"btBuilderProcess": { "btRuntimeProcess": {
"execCommand": "nix run github:nixos/nixpkgs#python312Packages.tensorboard -- --logdir ${dir_path}", "execCommand": "sleep 4",
"date": null, "date": null,
"status": null, "status": null,
"delay": 0, "delay": 0,
"checkCommand": null, "checkCommand": null,
"filter": null "filter": null
} }
} }

View file

@ -2,17 +2,23 @@ import { Disposable, Listener, TypedEvent } from "../helpers/typed_event";
import { GetRootDirUseCase } from "../usecases/get_root_dir_usecase" import { GetRootDirUseCase } from "../usecases/get_root_dir_usecase"
import { exec } from 'child_process'; import { exec } from 'child_process';
enum ProcessStatus { export const activeProcessPids: { [name: string]: { pid: number, status: ProcessStatus } } = {}
export enum ProcessStatus {
run = 'run', run = 'run',
endError = 'endError', endError = 'endError',
endOk = 'endOk', endOk = 'endOk',
userDelete = 'userDelete',
} }
class ExecutorProgramServiceV2 extends TypedEvent<{ [name: string]: { pid: number, status: ProcessStatus } }> {}
export const executorProgramServiceV2 = new ExecutorProgramServiceV2();
class ProcessData { class ProcessData {
log: string | undefined; log: string | undefined;
status: ProcessStatus; status: ProcessStatus;
constructor(log: string | undefined, status: ProcessStatus) { constructor(log: string | undefined, status: ProcessStatus) {
// console.log(log)
if (log !== undefined) { if (log !== undefined) {
this.log = log; this.log = log;
} }
@ -29,7 +35,6 @@ export abstract class ExecProcessWatcher extends TypedEvent<ProcessData> impleme
super(); super();
this.on((event) => { this.on((event) => {
if (event.log !== undefined) { if (event.log !== undefined) {
console.log(event.log)
this.logs.push(event.log); this.logs.push(event.log);
} }
this.status = event.status; this.status = event.status;
@ -42,17 +47,26 @@ export abstract class ExecProcessWatcher extends TypedEvent<ProcessData> impleme
} }
export class ExecProcessScenarioV2 { export class ExecProcessScenarioV2 {
call = (command: string, watcher: ExecProcessWatcher): void => { call = (command: string, watcher: ExecProcessWatcher, name?: string): void => {
const process = exec(command, { cwd: new GetRootDirUseCase().call() }); const process = exec(command, { cwd: new GetRootDirUseCase().call() });
if (process.pid) {
activeProcessPids[name ?? command] = { pid: process.pid, status: ProcessStatus.run };
executorProgramServiceV2.emit(activeProcessPids);
}
process.stdout.on('data', (data) => watcher.emit(new ProcessData(data, ProcessStatus.run))) process.stdout.on('data', (data) => watcher.emit(new ProcessData(data, ProcessStatus.run)))
process.stderr.on('data', (data) => watcher.emit(new ProcessData(data, ProcessStatus.run))); process.stderr.on('data', (data) => watcher.emit(new ProcessData(data, ProcessStatus.run)));
process.on('close', (code) =>{ process.on('close', (code) => {
if(code === 0){ if (code === 0) {
watcher.emit(new ProcessData(undefined, ProcessStatus.endOk)) watcher.emit(new ProcessData(undefined, ProcessStatus.endOk))
}else{ activeProcessPids[name ?? command] = { pid: process.pid ?? 0, status: ProcessStatus.endOk }
executorProgramServiceV2.emit(activeProcessPids);
} else {
watcher.emit(new ProcessData(undefined, ProcessStatus.endError)) watcher.emit(new ProcessData(undefined, ProcessStatus.endError))
activeProcessPids[name ?? command] = { pid: process.pid ?? 0, status: ProcessStatus.endError };
executorProgramServiceV2.emit(activeProcessPids);
} }
}); });
process.on('error', (err) => watcher.emit(new ProcessData(err.message, ProcessStatus.endError))); process.on('error', (err) => watcher.emit(new ProcessData(err.message, ProcessStatus.endError)));
} }
} }

View file

@ -7,6 +7,7 @@ import { ExecutorResult } from "../models/executor_result";
import { ExecutorProgramService } from "../services/executor_program_service"; import { ExecutorProgramService } from "../services/executor_program_service";
export const executorProgramService = new ExecutorProgramService(""); export const executorProgramService = new ExecutorProgramService("");
export class KillLastProcessUseCase extends CallbackStrategyWithEmpty { export class KillLastProcessUseCase extends CallbackStrategyWithEmpty {
call = async (): Promise<Result<undefined, string>> => { call = async (): Promise<Result<undefined, string>> => {
executorProgramService.deleteWorker(); executorProgramService.deleteWorker();

View file

@ -2,12 +2,13 @@ import { IsMongoId, IsNumber, IsString } from "class-validator";
import { IBehaviorTreeModel } from "./behavior_tree_database_model"; import { IBehaviorTreeModel } from "./behavior_tree_database_model";
export class BehaviorTreeValidationModel implements IBehaviorTreeModel { export class BehaviorTreeValidationModel implements IBehaviorTreeModel {
_id: string;
@IsString() @IsString()
public name: string; public name: string;
@IsMongoId() @IsMongoId()
public project:string; public project: string;
public skills:any; public skills: any;
public xml:string; public xml: string;
@IsNumber() @IsNumber()
public unixTime: number public unixTime: number
} }

View file

@ -15,6 +15,7 @@ export enum ProcessStatus {
END = "END", END = "END",
ERROR = "ERROR", ERROR = "ERROR",
NEW = "NEW", NEW = "NEW",
} }
export interface IDatasetModel { export interface IDatasetModel {
_id?: string; _id?: string;

View file

@ -1,7 +1,53 @@
import { CallbackStrategyWithEmpty, ResponseBase } from "../../../core/controllers/http_controller"; import {
CallbackStrategyWithIdQuery,
CallbackStrategyWithValidationModel,
ResponseBase,
} from "../../../core/controllers/http_controller";
import { StaticFilesProject } from "../../../core/models/static_files";
import {
ExecProcessScenarioV2,
ExecProcessWatcher,
activeProcessPids,
} from "../../../core/scenarios/exec_process_scenario_v2";
import { ReadByIdDataBaseModelScenario } from "../../../core/scenarios/read_by_id_database_model_scenario";
import { SearchOneDataBaseModelUseCase } from "../../../core/usecases/search_database_model_usecase";
import { CoreValidation } from "../../../core/validations/core_validation";
import { MongoIdValidation } from "../../../core/validations/mongo_id_validation";
import { BehaviorTreeDBModel } from "../../behavior_trees/models/behavior_tree_database_model";
import { BehaviorTreeValidationModel } from "../../behavior_trees/models/behavior_tree_validation_model";
import { IProjectModel, ProjectDBModel } from "../../projects/models/project_model_database_model";
import { GetCommandScenario } from "./get_command_scenario";
export class ExecBtBuilderUseCase extends CallbackStrategyWithEmpty { class ProcessWatcher extends ExecProcessWatcher {
call(): ResponseBase { async result(): Promise<any> {
throw new Error("Method not implemented."); console.log(this);
} }
} }
export enum Proceed {
EXEC_BT = "EXEC_BT",
}
export class ExecBtScenario extends CallbackStrategyWithIdQuery {
idValidationExpression: CoreValidation = new MongoIdValidation();
call = async (id: string): ResponseBase =>
(await new ReadByIdDataBaseModelScenario<BehaviorTreeValidationModel>(BehaviorTreeDBModel).call(id)).map(
async (behaviorTreeValidationModel) =>
(
await new SearchOneDataBaseModelUseCase<IProjectModel>(ProjectDBModel).call(
{ isActive: true },
"no active projects"
)
).map(async (model) =>
(await new GetCommandScenario().call("btRuntimeProcess")).map((execProcessData) =>
new ExecProcessScenarioV2().call(
execProcessData.execCommand.replace(
"${bt_path}",
`${model.rootDir}/${StaticFilesProject.behaviorTrees}/${behaviorTreeValidationModel.name}/bt.xml`
),
new ProcessWatcher(),
Proceed.EXEC_BT
)
)
)
);
}

View file

@ -0,0 +1,7 @@
import { CallbackStrategyWithEmpty, ResponseBase } from "../../../core/controllers/http_controller";
import { Result } from "../../../core/helpers/result";
import { activeProcessPids } from "../../../core/scenarios/exec_process_scenario_v2";
export class GetRunTimeStatuses extends CallbackStrategyWithEmpty {
call = async (): ResponseBase => Result.ok(activeProcessPids);
}

View file

@ -1,10 +0,0 @@
import { CallbackStrategyWithEmpty, ResponseBase } from "../../../core/controllers/http_controller";
import { Result } from "../../../core/helpers/result";
import { SpawnProcessUseCase } from "../../../core/usecases/exec_process_usecase";
import { GetCommandScenario } from "./get_command_scenario";
import { ProcessWatcher } from "../service/process_watcher";
import { App } from "../../../core/controllers/app";
export class GetSimulationStateScenario extends CallbackStrategyWithEmpty {
call = async (): ResponseBase => Result.ok("");
}

View file

@ -0,0 +1,51 @@
import { IsString } from "class-validator";
import { CallbackStrategyWithValidationModel, ResponseBase } from "../../../core/controllers/http_controller";
import { ProcessStatus, activeProcessPids } from "../../../core/scenarios/exec_process_scenario_v2";
import { Result } from "../../../core/helpers/result";
import { GetRootDirUseCase } from "../../../core/usecases/get_root_dir_usecase";
import { exec } from 'child_process';
export class StopProcessModel {
@IsString()
pid: string
}
export class StopProcessUseCase extends CallbackStrategyWithValidationModel<StopProcessModel> {
validationModel: StopProcessModel = new StopProcessModel();
call = async (model: StopProcessModel): ResponseBase => {
try {
if (activeProcessPids[model.pid] === undefined) {
return Result.error('missing pid');
}
const processKillStatus = await new Promise<string>((resolve, reject) => {
try {
exec(`kill ${activeProcessPids[model.pid].pid}`, { cwd: new GetRootDirUseCase().call() }, (error, stdout, stderr) => {
if (error) {
reject(`Ошибка: ${stderr}`); return;
}
if (stderr) {
reject(`Ошибка: ${stderr}`);
return;
}
resolve('Process kill')
});
} catch (e) {
resolve('Process kill')
}
})
if (processKillStatus == 'Process kill') {
activeProcessPids[model.pid].status = ProcessStatus.userDelete;
}
return Result.ok(processKillStatus);
} catch (error) {
return Result.ok(error)
}
}
}

View file

@ -11,4 +11,4 @@ export interface ExecProcess {
delay: number; delay: number;
checkCommand: null; checkCommand: null;
filter: null; filter: null;
} }

View file

@ -1,42 +1,19 @@
import { CrudController } from "../../core/controllers/crud_controller";
import { ExecRunTimeCommandValidationModel } from "./model/run_time_validation_model"; import { ExecRunTimeCommandValidationModel } from "./model/run_time_validation_model";
import { ExecRuntimeDatabaseModel } from "./model/run_time_database_model"; import { CoreHttpController, SubRouter, CallbackStrategyWithIdQuery } from "../../core/controllers/http_controller";
import { CoreHttpController, SubRouter, HttpMethodType, CallbackStrategyWithIdQuery, ResponseBase } from "../../core/controllers/http_controller"; import { ExecBtScenario } from "./domain/exec_bt_builder_usecase";
import { ExecBtBuilderUseCase } from "./domain/exec_bt_builder_usecase"; import { GetRunTimeStatuses } from "./domain/get_run_time_statuses_usecase";
import { ExecSimulationUseCase } from "./domain/exec_simulation_usecase"; import { StopProcessUseCase } from "./domain/stop_process_usecase";
import { GetBtBuilderStateUseCase } from "./domain/get_bt_builder_status_usecase";
import { GetSimulationStateScenario } from "./domain/get_simulation_state_usecase";
import { MongoIdValidation } from "../../core/validations/mongo_id_validation";
import { CoreValidation } from "../../core/validations/core_validation";
import { ReadByIdDataBaseModelUseCase } from "../../core/usecases/read_by_id_database_model_usecase";
import { ICalculationInstance, CalculationInstanceDBModel } from "../calculations_instance/models/calculations_instance_database_model";
import { Result } from "../../core/helpers/result";
import { SpawnProcessUseCase } from "../../core/usecases/exec_process_usecase";
import { ProcessWatcher } from "./service/process_watcher";
class ExecAnalyzeScenario extends CallbackStrategyWithIdQuery {
idValidationExpression: CoreValidation = new MongoIdValidation() export class RunTimePresentation extends CoreHttpController<ExecRunTimeCommandValidationModel> {
call = async (id: string) =>
(await new ReadByIdDataBaseModelUseCase<ICalculationInstance>(CalculationInstanceDBModel).call(id)).map(async (model) =>
(await new SpawnProcessUseCase().call('/Users/idontsudo/webservice', `nix run github:nixos/nixpkgs#python312Packages.tensorboard -- --logdir ${model.instancePath}`, "", new ProcessWatcher())).map(() => Result.ok('ok'),),
)
}
export class RunTimePresentation extends CrudController<ExecRunTimeCommandValidationModel, any> {
constructor() { constructor() {
super({ super({
url: "run_time", url: "run_time",
}); });
this.subRoutes.push(new SubRouter("POST", "/exec/bt/builder", new ExecBtBuilderUseCase())); this.subRoutes.push(new SubRouter<any>("POST", "exec/bt", new ExecBtScenario()));
this.subRoutes.push(new SubRouter("POST", "/get/bt/builder/state", new GetBtBuilderStateUseCase())); this.subRoutes.push(new SubRouter("GET", "status", new GetRunTimeStatuses()));
this.subRoutes.push(new SubRouter("POST", "/get/simulator/state", new GetSimulationStateScenario())); this.subRoutes.push(new SubRouter<any>("POST", "kill", new StopProcessUseCase()))
this.subRoutes.push(new SubRouter('POST', "exec/analyze", new ExecAnalyzeScenario()))
this.subRoutes.push({
method: "POST",
subUrl: "/exec/simulation/",
fn: new ExecSimulationUseCase(),
});
} }
} }

View file

@ -5,10 +5,11 @@ import { extensions } from "./core/extensions/extensions";
import { httpRoutes } from "./core/controllers/routes"; import { httpRoutes } from "./core/controllers/routes";
import { executorProgramService } from "./core/usecases/exec_process_usecase"; import { executorProgramService } from "./core/usecases/exec_process_usecase";
import { main } from "./p"; import { main } from "./p";
import { executorProgramServiceV2 } from "./core/scenarios/exec_process_scenario_v2";
extensions(); extensions();
const socketSubscribers = [new SocketSubscriber(executorProgramService, "realtime")]; const socketSubscribers = [new SocketSubscriber(executorProgramService, "realtime"), new SocketSubscriber(executorProgramServiceV2, 'realtimeV2',)];
new App(httpRoutes, socketSubscribers).listen(); new App(httpRoutes, socketSubscribers).listen();
// main() // main()

View file

@ -1,23 +1,27 @@
import { Socket, io } from "socket.io-client"; import { Socket, io } from "socket.io-client";
import { Result } from "../helper/result"; import { Result } from "../helper/result";
import { TypedEvent } from "../helper/typed_event"; import { TypedEvent } from "../helper/typed_event";
import { RuntimeModel } from "../../features/behavior_tree_builder/presentation/ui/actions/runtime_actions";
export class SocketRepository extends TypedEvent<any> { export class SocketRepository extends TypedEvent<any> {
serverURL = "ws://localhost:4001"; serverURL = "ws://localhost:4001";
socket: Socket | undefined; socket: Socket | undefined;
async connect():Promise<Result<boolean, boolean>> { async connect(): Promise<Result<boolean, boolean>> {
const socket = io(this.serverURL); const socket = io(this.serverURL);
this.socket = socket; this.socket = socket;
socket.connect(); socket.connect();
socket.on('realtime', (d) =>{ socketCoreInstances.forEach((el) => {
socket.on(el.event, (event) => el.emit(event))
})
socket.on('realtime', (d) => {
this.emit({ this.emit({
event:"realtime", event: "realtime",
payload:d payload: d
}) })
}) })
if(socket.connected){ if (socket.connected) {
return Result.ok(true) return Result.ok(true)
} }
return Result.error(false) return Result.error(false)
@ -25,4 +29,15 @@ export class SocketRepository extends TypedEvent<any> {
} }
export const socketRepository = new SocketRepository() export const socketRepository = new SocketRepository()
export abstract class SocketCore<T> extends TypedEvent<T> {
abstract event: string
}
export class RunTimeSocketRepository extends SocketCore<RuntimeModel> {
event = 'realtimeV2';
}
export const runTimeSocketRepository = new RunTimeSocketRepository();
const socketCoreInstances: SocketCore<any>[] = [runTimeSocketRepository]

View file

@ -17,6 +17,7 @@ interface IMessage {
errorMessage?: string; errorMessage?: string;
} }
export abstract class UiLoader { export abstract class UiLoader {
navigate?: NavigateFunction;
isLoading = false; isLoading = false;
async httpHelper<T>(callBack: Promise<Result<any, T>>) { async httpHelper<T>(callBack: Promise<Result<any, T>>) {
this.isLoading = true; this.isLoading = true;
@ -75,7 +76,7 @@ export abstract class UiErrorState<T> extends UiLoader {
console.log(error); console.log(error);
}; };
abstract init(navigate?: NavigateFunction): Promise<any>; abstract init(navigate?: NavigateFunction): Promise<any>;
dispose() {} dispose() { }
errors: UiBaseError[] = []; errors: UiBaseError[] = [];
} }

View file

@ -931,6 +931,18 @@ const getIconSvg = (
/> />
</svg> </svg>
); );
case "3points":
return Result.ok(
<svg
xmlns="http://www.w3.org/2000/svg"
width={width ? width : "20"}
height={height ? height : "20"}
viewBox="0 0 128 512"
>
<path d="M64 360a56 56 0 1 0 0 112 56 56 0 1 0 0-112zm0-160a56 56 0 1 0 0 112 56 56 0 1 0 0-112zM120 96A56 56 0 1 0 8 96a56 56 0 1 0 112 0z" />
</svg>
);
case "Move": case "Move":
return Result.ok( return Result.ok(
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" xmlSpace="preserve"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" xmlSpace="preserve">

View file

@ -107,7 +107,7 @@ export const MainPageV2: React.FC<{
overflow: "auto", overflow: "auto",
}} }}
> >
<div {/* <div
style={{ style={{
alignSelf: "center", alignSelf: "center",
width: 645, width: 645,
@ -222,7 +222,7 @@ export const MainPageV2: React.FC<{
> >
{rightChild} {rightChild}
</div> </div>
</div> </div> */}
<div style={Object.assign({ width: "100%" }, style)}>{children}</div> <div style={Object.assign({ width: "100%" }, style)}>{children}</div>
</div> </div>

View file

@ -0,0 +1,46 @@
import React, { useEffect, useRef, useState } from "react";
import styled from "styled-components";
interface PopoverProps {
content: React.ReactNode;
children: React.ReactNode;
}
const PopoverV2: React.FC<PopoverProps> = ({ content, children }) => {
const [visible, setVisible] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const togglePopover = () => {
setVisible((prev) => !prev);
};
return (
<div
style={{
position: "relative",
display: "inline-block",
}}
onClick={() => togglePopover()}
>
{children}
<div
style={{
position: "absolute",
backgroundColor: "white",
border: "1px solid #ccc;",
borderRadius: "4px",
padding: "10px;",
zIndex: "1000",
transition: "opacity 0.2s ease, visibility 0.2s ease",
opacity: visible ? 1 : 0,
visibility: visible ? "visible" : "hidden",
}}
>
{content}
</div>
</div>
);
};
// visibility: ${({ visible }) => (visible ? 'visible' : 'hidden')};
// opacity: ${({ visible }) => (visible ? 1 : 0)};
// transition: opacity 0.2s ease, visibility 0.2s ease;
export default PopoverV2;

View file

@ -4,20 +4,15 @@ import { createEditor } from "./ui/editor/editor";
import { SkillTree } from "./ui/skill_tree/skill_tree"; import { SkillTree } from "./ui/skill_tree/skill_tree";
import { BehaviorTreeBuilderStore, DrawerState } from "./behavior_tree_builder_store"; import { BehaviorTreeBuilderStore, DrawerState } from "./behavior_tree_builder_store";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { match } from "ts-pattern";
import { Icon } from "../../../core/ui/icons/icons"; import { Icon } from "../../../core/ui/icons/icons";
import { CoreText, CoreTextType } from "../../../core/ui/text/text"; import { CoreText, CoreTextType } from "../../../core/ui/text/text";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { IForms, forms } from "./ui/forms/forms"; import { IForms, forms } from "./ui/forms/forms";
import { ButtonV2, ButtonV2Type } from "../../../core/ui/button/button_v2";
import { CoreCard } from "../../../core/ui/card/card";
import { themeStore } from "../../.."; import { themeStore } from "../../..";
import { CoreModal } from "../../../core/ui/modal/modal";
import { InputV2 } from "../../../core/ui/input/input_v2";
import { SelectV2 } from "../../../core/ui/select/select_v2";
import { MainPageV2 } from "../../../core/ui/pages/main_page_v2"; import { MainPageV2 } from "../../../core/ui/pages/main_page_v2";
import { DrawerV2 } from "../../../core/ui/drawer/drawer"; import { DrawerV2 } from "../../../core/ui/drawer/drawer";
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"; import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
import PopoverV2 from "../../../core/ui/popover/popover";
export const behaviorTreeBuilderScreenPath = "behavior/tree/screen/path"; export const behaviorTreeBuilderScreenPath = "behavior/tree/screen/path";
@ -66,54 +61,36 @@ export const BehaviorTreeBuilderScreen = observer(() => {
}, []); }, []);
return ( return (
<MainPageV2 <MainPageV2
rightChild={
<>
<div
style={{
width: "100%",
display: "flex",
flexDirection: "row",
justifyContent: "end",
paddingRight: 30,
alignItems: "center",
}}
>
{/* <ButtonV2 style={{ height: 40 }} onClick={() => {}} text="Запуск" textColor={themeStore.theme.black} />
<div style={{ width: 10 }} />
<ButtonV2
style={{ height: 40 }}
onClick={() => {}}
text="Стоп"
type={ButtonV2Type.empty}
textColor={themeStore.theme.greenWhite}
/>
<div style={{ width: 10 }} /> */}
{store.isNeedSaveBtn ? (
<div
style={{
backgroundColor: store.isNeedSaveBtn ? themeStore.theme.greenWhite : undefined,
height: 40,
textAlign: "center",
alignContent: "center",
width: 40,
borderRadius: 100,
}}
>
{store.isNeedSaveBtn ? (
<Icon style={{ height: 21 }} onClick={() => store.onClickSaveBehaviorTree()} type="Floppy" />
) : undefined}
</div>
) : (
<></>
)}
</div>
</>
}
style={{ position: "absolute", height: "100%", overflow: "hidden" }} style={{ position: "absolute", height: "100%", overflow: "hidden" }}
bgColor={themeStore.theme.black} bgColor={themeStore.theme.black}
children={ children={
<> <>
<> <>
<div style={{ position: "absolute", zIndex: 100 }}>
<div
style={{
width: "100%",
display: "flex",
flexDirection: "row",
justifyContent: "end",
paddingRight: 30,
alignItems: "center",
}}
>
<div
style={{
backgroundColor: themeStore.theme.greenWhite,
height: 40,
textAlign: "center",
alignContent: "center",
width: 40,
borderRadius: 100,
}}
>
<Icon style={{ height: 21 }} onClick={() => store.onClickSaveBehaviorTree()} type="Floppy" />
</div>
</div>
</div>
<div <div
style={{ style={{
height: "100%", height: "100%",
@ -148,7 +125,7 @@ export const BehaviorTreeBuilderScreen = observer(() => {
width: "calc(100% - 300px)", width: "calc(100% - 300px)",
}} }}
> >
{/* <PanelGroup direction="horizontal" style={{ width: "100%" }}> <PanelGroup direction="horizontal" style={{ width: "100%" }}>
<Panel> <Panel>
<div <div
ref={ref} ref={ref}
@ -160,18 +137,46 @@ export const BehaviorTreeBuilderScreen = observer(() => {
}} }}
/> />
</Panel> </Panel>
{store.panels.map((el) => ( {store.panels.map((el, index) => (
<> <>
<PanelResizeHandle> <PanelResizeHandle
<div style={{ width: "100%", color: "white" }}>BANE</div> children={
<>
<div style={{ width: 10, height: "100%", backgroundColor: "beige" }}></div>
</>
}
></PanelResizeHandle>
<Panel
style={{ backgroundColor: "rgb(29, 27, 32)" }}
defaultSize={el.size}
onResize={(size) => store.panelResize(size, index)}
>
<div <div
style={{ width: 10, height: "100%", backgroundColor: "beige", border: "1px solid black" }} style={{
></div> display: "flex",
</PanelResizeHandle> width: "100%",
<Panel defaultSize={0}> </Panel> justifyContent: "space-between",
backgroundColor: "beige",
}}
>
<PopoverV2
children={<Icon type={"3points"} height={26} />}
content={
<div style={{ width: "max-content", padding: 10 }}>
{store.panelActions.map((el, i) => (
<CoreText type={CoreTextType.medium} text={el.name} onClick={() => el.action(index)} />
))}
</div>
}
/>
<div style={{ color: "black", alignContent: "center", paddingRight: 10 }}>{el.name}</div>
</div>
{el.body}
</Panel>
</> </>
))} ))}
</PanelGroup> */} </PanelGroup>
</div> </div>
</> </>

View file

@ -26,6 +26,7 @@ import { BehaviorTreeModel } from "../model/behavior_tree_model";
import { PrimitiveViewModel, SystemPrimitive } from "../model/primitive_view_model"; import { PrimitiveViewModel, SystemPrimitive } from "../model/primitive_view_model";
import { SceneAsset } from "../../../core/model/scene_asset"; import { SceneAsset } from "../../../core/model/scene_asset";
import { themeStore } from "../../.."; import { themeStore } from "../../..";
import { RunTimeActions } from "./ui/actions/runtime_actions";
interface I2DArea { interface I2DArea {
x: number; x: number;
@ -37,8 +38,31 @@ interface I2DArea {
export enum DrawerState { export enum DrawerState {
editThreadBehaviorTree = "Редактирование", editThreadBehaviorTree = "Редактирование",
} }
interface IActionPanel {
name: string;
selectIsClosePopover?: boolean;
action: (index: number) => void;
}
export class PanelBody {
body?: React.ReactNode;
size: number = 0;
name: string = "выберите тип панели";
constructor(body: React.ReactNode | undefined, name: string | undefined, size: number | undefined) {
makeAutoObservable(this);
if (name) this.name = name;
if (body) this.body = body;
if (size) this.size = size;
}
}
export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeViewModel, CoreError> { export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeViewModel, CoreError> {
panelActions: IActionPanel[] = [
{ name: "Добавить панель", action: () => this.addNewPanel() },
{ name: "Убрать панель", action: (index) => this.removePanel(index) },
{ name: "Runtime", action: (index) => this.changePanel(index, "Runtime", <RunTimeActions />) },
];
sceneAsset?: SceneAsset; sceneAsset?: SceneAsset;
viewModel: BehaviorTreeViewModel = BehaviorTreeViewModel.empty(); viewModel: BehaviorTreeViewModel = BehaviorTreeViewModel.empty();
behaviorTreeModel: BehaviorTreeModel = BehaviorTreeModel.empty(); behaviorTreeModel: BehaviorTreeModel = BehaviorTreeModel.empty();
@ -51,11 +75,10 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
activeProject: string = ""; activeProject: string = "";
behaviorTreeBuilderHttpRepository = new BehaviorTreeBuilderHttpRepository(); behaviorTreeBuilderHttpRepository = new BehaviorTreeBuilderHttpRepository();
canRun = true; canRun = true;
isNeedSaveBtn = false; isNeedSaveBtn = true;
selected: string = ""; selected: string = "";
selectedSid: string = ""; selectedSid: string = "";
nodeBehaviorTree: NodeBehaviorTree[] = []; nodeBehaviorTree: NodeBehaviorTree[] = [];
navigate?: NavigateFunction;
editor?: NodeEditor<Schemes>; editor?: NodeEditor<Schemes>;
areaPlugin?: AreaPlugin<Schemes, AreaExtra>; areaPlugin?: AreaPlugin<Schemes, AreaExtra>;
nodeUpdateObserver?: NodeRerenderObserver; nodeUpdateObserver?: NodeRerenderObserver;
@ -70,8 +93,12 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
}, },
], ],
}; };
panels = [1, 1, 1, 1]; panels: PanelBody[] = [new PanelBody(<RunTimeActions />, "Runtime", 200)];
addNewPanel = () => this.panels.push(new PanelBody(undefined, undefined, undefined));
removePanel = (index: number) =>
this.panels.length !== 1
? (this.panels = this.panels.filter((_, i) => i !== index))
: message.error("должна быть хоть одна панель");
constructor() { constructor() {
super(DrawerState); super(DrawerState);
makeAutoObservable(this); makeAutoObservable(this);
@ -116,7 +143,7 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
name: name, name: name,
id: sid, id: sid,
}); });
this.isNeedSaveBtn = true; // this.isNeedSaveBtn = true;
if (!name.isEqualMany(Object.keys(SystemPrimitive))) { if (!name.isEqualMany(Object.keys(SystemPrimitive))) {
this.skillTemplates?.getSkill(name).fold( this.skillTemplates?.getSkill(name).fold(
(skill) => { (skill) => {
@ -346,4 +373,11 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
); );
} }
changeSceneViewModel = (text: string) => {}; changeSceneViewModel = (text: string) => {};
changePanel = (index: number, name: string, body?: React.ReactNode) => {
this.panels = this.panels.replacePropIndex({ name: name, body: body }, index);
};
panelResize = (size: number, index: number) => {
this.panels.replacePropIndex({ size: size }, index);
};
} }

View file

@ -0,0 +1,94 @@
import { NavigateFunction, useParams } from "react-router-dom";
import { CoreHttpRepository, HttpMethod } from "../../../../../core/repository/core_http_repository";
import { CoreError, UiErrorState } from "../../../../../core/store/base_store";
import { ProcessStatus } from "../../../../dataset/dataset_model";
import makeAutoObservable from "mobx-store-inheritance";
import { useStore } from "../../../../../core/helper/use_store";
import { Loader } from "../../../../../core/ui/loader/loader";
import { useEffect } from "react";
import {
RunTimeSocketRepository,
runTimeSocketRepository,
} from "../../../../../core/repository/core_socket_repository";
import { observer } from "mobx-react-lite";
interface IPid {
pid: string;
}
export interface RuntimeModel {
[name: string]: { pid: number; status: String };
}
export class RunTimeHttpRepository extends CoreHttpRepository {
feature = "/run_time";
stop = (model: IPid) => this._jsonRequest(HttpMethod.POST, this.feature + "/kill", model);
getStatuses = () => this._jsonRequest<RuntimeModel>(HttpMethod.GET, this.feature + "/status");
execBt = (id: string) => this._jsonRequest(HttpMethod.POST, this.feature + `/exec/bt?id=${id}`);
}
// export class RunTimeSocketRepository extends SockeCore {}
export class RunTimeStore extends UiErrorState<CoreError> {
runTimeHttpRepository: RunTimeHttpRepository = new RunTimeHttpRepository();
runTimeSocketRepository: RunTimeSocketRepository = runTimeSocketRepository;
pageId: string;
runTime?: RuntimeModel = {};
constructor() {
super();
makeAutoObservable(this);
runTimeSocketRepository.on((event) => (console.log(event), (this.runTime = event)));
}
async init(navigate?: NavigateFunction | undefined): Promise<any> {
this.mapOk("runTime", this.runTimeHttpRepository.getStatuses());
this.navigate = navigate;
}
executeBt = () => this.runTimeHttpRepository.execBt(this.pageId);
initParam = (id: string) => (this.pageId = id);
isExecuteBt = (): boolean => {
if (this.runTime === undefined) {
return false;
}
if (
this.runTime["EXEC_BT"] !== undefined &&
this.runTime["EXEC_BT"]["status"] !== undefined &&
this.runTime["EXEC_BT"]["status"] === "endOk"
) {
return false;
}
return true;
};
}
export const RunTimeActions = observer(() => {
const store = useStore(RunTimeStore);
const params = useParams();
useEffect(() => {
store.initParam(params.id as string);
}, []);
return (
<div style={{ width: "100%" }}>
{store.isLoading ? (
<Loader />
) : (
<>
{store.isExecuteBt() ? (
<>
<div
style={{ justifyContent: "center", color: "white", justifySelf: "center" }}
onClick={() => store.executeBt()}
>
Дерево запущено
</div>
</>
) : (
<>
<div
style={{ justifyContent: "center", color: "white", justifySelf: "center" }}
onClick={() => store.executeBt()}
>
Запустить дерево
</div>
</>
)}
</>
)}
</div>
);
});

View file

@ -12,7 +12,6 @@ export enum DrawerState {
} }
export class BehaviorTreeManagerStore extends UiDrawerFormState<BehaviorTreeViewModel, CoreError> { export class BehaviorTreeManagerStore extends UiDrawerFormState<BehaviorTreeViewModel, CoreError> {
viewModel: BehaviorTreeViewModel = BehaviorTreeViewModel.empty(); viewModel: BehaviorTreeViewModel = BehaviorTreeViewModel.empty();
navigate?: NavigateFunction;
btTreeModels: BehaviorTreeModel[] = []; btTreeModels: BehaviorTreeModel[] = [];
scenes?: SceneModel[]; scenes?: SceneModel[];
behaviorTreeManagerHttpRepository = new BehaviorTreeManagerHttpRepository(); behaviorTreeManagerHttpRepository = new BehaviorTreeManagerHttpRepository();

View file

@ -26,7 +26,6 @@ export class PoseEstimateRepository extends HttpRepository {
execAnalyze = (id: string) => this._jsonRequest(HttpMethod.POST, `/run_time/exec/analyze?id=${id}`); execAnalyze = (id: string) => this._jsonRequest(HttpMethod.POST, `/run_time/exec/analyze?id=${id}`);
} }
export class PoseEstimateStore extends UiErrorState<any> { export class PoseEstimateStore extends UiErrorState<any> {
navigate?: NavigateFunction;
poseEstimateRepository = new PoseEstimateRepository(); poseEstimateRepository = new PoseEstimateRepository();
constructor() { constructor() {
super(); super();