diff --git a/.vscode/settings.json b/.vscode/settings.json index 2793cf8..58a804b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,6 +20,7 @@ "GLTF", "grau", "idontsudo", + "Pids", "raycaster", "skils", "typedataset", diff --git a/server/command.json b/server/command.json index 468f1fe..20c1c9c 100644 --- a/server/command.json +++ b/server/command.json @@ -15,12 +15,12 @@ "checkCommand": null, "filter": null }, - "btBuilderProcess": { - "execCommand": "nix run github:nixos/nixpkgs#python312Packages.tensorboard -- --logdir ${dir_path}", + "btRuntimeProcess": { + "execCommand": "sleep 4", "date": null, "status": null, "delay": 0, "checkCommand": null, "filter": null } -} \ No newline at end of file +} diff --git a/server/src/core/scenarios/exec_process_scenario_v2.ts b/server/src/core/scenarios/exec_process_scenario_v2.ts index 9f00e97..cd846d4 100644 --- a/server/src/core/scenarios/exec_process_scenario_v2.ts +++ b/server/src/core/scenarios/exec_process_scenario_v2.ts @@ -2,17 +2,23 @@ import { Disposable, Listener, TypedEvent } from "../helpers/typed_event"; import { GetRootDirUseCase } from "../usecases/get_root_dir_usecase" import { exec } from 'child_process'; -enum ProcessStatus { +export const activeProcessPids: { [name: string]: { pid: number, status: ProcessStatus } } = {} + +export enum ProcessStatus { run = 'run', endError = 'endError', endOk = 'endOk', + userDelete = 'userDelete', } +class ExecutorProgramServiceV2 extends TypedEvent<{ [name: string]: { pid: number, status: ProcessStatus } }> {} + +export const executorProgramServiceV2 = new ExecutorProgramServiceV2(); class ProcessData { log: string | undefined; status: ProcessStatus; constructor(log: string | undefined, status: ProcessStatus) { - // console.log(log) + if (log !== undefined) { this.log = log; } @@ -29,7 +35,6 @@ export abstract class ExecProcessWatcher extends TypedEvent impleme super(); this.on((event) => { if (event.log !== undefined) { - console.log(event.log) this.logs.push(event.log); } this.status = event.status; @@ -42,17 +47,26 @@ export abstract class ExecProcessWatcher extends TypedEvent impleme } 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() }); + 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.stderr.on('data', (data) => watcher.emit(new ProcessData(data, ProcessStatus.run))); - process.on('close', (code) =>{ - if(code === 0){ + process.on('close', (code) => { + if (code === 0) { 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)) + 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))); } } \ No newline at end of file diff --git a/server/src/core/usecases/exec_process_usecase.ts b/server/src/core/usecases/exec_process_usecase.ts index a6899dc..faa140e 100644 --- a/server/src/core/usecases/exec_process_usecase.ts +++ b/server/src/core/usecases/exec_process_usecase.ts @@ -7,6 +7,7 @@ import { ExecutorResult } from "../models/executor_result"; import { ExecutorProgramService } from "../services/executor_program_service"; export const executorProgramService = new ExecutorProgramService(""); + export class KillLastProcessUseCase extends CallbackStrategyWithEmpty { call = async (): Promise> => { executorProgramService.deleteWorker(); diff --git a/server/src/features/behavior_trees/models/behavior_tree_validation_model.ts b/server/src/features/behavior_trees/models/behavior_tree_validation_model.ts index e729b67..06f103c 100644 --- a/server/src/features/behavior_trees/models/behavior_tree_validation_model.ts +++ b/server/src/features/behavior_trees/models/behavior_tree_validation_model.ts @@ -2,12 +2,13 @@ import { IsMongoId, IsNumber, IsString } from "class-validator"; import { IBehaviorTreeModel } from "./behavior_tree_database_model"; export class BehaviorTreeValidationModel implements IBehaviorTreeModel { + _id: string; @IsString() public name: string; @IsMongoId() - public project:string; - public skills:any; - public xml:string; + public project: string; + public skills: any; + public xml: string; @IsNumber() public unixTime: number } diff --git a/server/src/features/datasets/models/dataset_validation_model.ts b/server/src/features/datasets/models/dataset_validation_model.ts index 5486ace..abd6e84 100644 --- a/server/src/features/datasets/models/dataset_validation_model.ts +++ b/server/src/features/datasets/models/dataset_validation_model.ts @@ -15,6 +15,7 @@ export enum ProcessStatus { END = "END", ERROR = "ERROR", NEW = "NEW", + } export interface IDatasetModel { _id?: string; diff --git a/server/src/features/runtime/domain/exec_bt_builder_usecase.ts b/server/src/features/runtime/domain/exec_bt_builder_usecase.ts index 66ea474..dd243ec 100644 --- a/server/src/features/runtime/domain/exec_bt_builder_usecase.ts +++ b/server/src/features/runtime/domain/exec_bt_builder_usecase.ts @@ -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 { - call(): ResponseBase { - throw new Error("Method not implemented."); +class ProcessWatcher extends ExecProcessWatcher { + async result(): Promise { + 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(BehaviorTreeDBModel).call(id)).map( + async (behaviorTreeValidationModel) => + ( + await new SearchOneDataBaseModelUseCase(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 + ) + ) + ) + ); +} diff --git a/server/src/features/runtime/domain/get_run_time_statuses_usecase.ts b/server/src/features/runtime/domain/get_run_time_statuses_usecase.ts new file mode 100644 index 0000000..2967c31 --- /dev/null +++ b/server/src/features/runtime/domain/get_run_time_statuses_usecase.ts @@ -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); +} \ No newline at end of file diff --git a/server/src/features/runtime/domain/get_simulation_state_usecase.ts b/server/src/features/runtime/domain/get_simulation_state_usecase.ts deleted file mode 100644 index 4658739..0000000 --- a/server/src/features/runtime/domain/get_simulation_state_usecase.ts +++ /dev/null @@ -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(""); -} diff --git a/server/src/features/runtime/domain/stop_process_usecase.ts b/server/src/features/runtime/domain/stop_process_usecase.ts new file mode 100644 index 0000000..4e52d90 --- /dev/null +++ b/server/src/features/runtime/domain/stop_process_usecase.ts @@ -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 { + 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((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) + } + + + } + +} \ No newline at end of file diff --git a/server/src/features/runtime/model/command.ts b/server/src/features/runtime/model/command.ts index 863d14e..3334f20 100644 --- a/server/src/features/runtime/model/command.ts +++ b/server/src/features/runtime/model/command.ts @@ -11,4 +11,4 @@ export interface ExecProcess { delay: number; checkCommand: null; filter: null; -} +} \ No newline at end of file diff --git a/server/src/features/runtime/runtime_presentation.ts b/server/src/features/runtime/runtime_presentation.ts index 335deee..9804b39 100644 --- a/server/src/features/runtime/runtime_presentation.ts +++ b/server/src/features/runtime/runtime_presentation.ts @@ -1,42 +1,19 @@ -import { CrudController } from "../../core/controllers/crud_controller"; + import { ExecRunTimeCommandValidationModel } from "./model/run_time_validation_model"; -import { ExecRuntimeDatabaseModel } from "./model/run_time_database_model"; -import { CoreHttpController, SubRouter, HttpMethodType, CallbackStrategyWithIdQuery, ResponseBase } from "../../core/controllers/http_controller"; -import { ExecBtBuilderUseCase } from "./domain/exec_bt_builder_usecase"; -import { ExecSimulationUseCase } from "./domain/exec_simulation_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"; +import { CoreHttpController, SubRouter, CallbackStrategyWithIdQuery } from "../../core/controllers/http_controller"; +import { ExecBtScenario } from "./domain/exec_bt_builder_usecase"; +import { GetRunTimeStatuses } from "./domain/get_run_time_statuses_usecase"; +import { StopProcessUseCase } from "./domain/stop_process_usecase"; -class ExecAnalyzeScenario extends CallbackStrategyWithIdQuery { - idValidationExpression: CoreValidation = new MongoIdValidation() - call = async (id: string) => - (await new ReadByIdDataBaseModelUseCase(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 { +export class RunTimePresentation extends CoreHttpController { constructor() { super({ url: "run_time", }); - this.subRoutes.push(new SubRouter("POST", "/exec/bt/builder", new ExecBtBuilderUseCase())); - this.subRoutes.push(new SubRouter("POST", "/get/bt/builder/state", new GetBtBuilderStateUseCase())); - this.subRoutes.push(new SubRouter("POST", "/get/simulator/state", new GetSimulationStateScenario())); - this.subRoutes.push(new SubRouter('POST', "exec/analyze", new ExecAnalyzeScenario())) - this.subRoutes.push({ - method: "POST", - subUrl: "/exec/simulation/", - fn: new ExecSimulationUseCase(), - }); + this.subRoutes.push(new SubRouter("POST", "exec/bt", new ExecBtScenario())); + this.subRoutes.push(new SubRouter("GET", "status", new GetRunTimeStatuses())); + this.subRoutes.push(new SubRouter("POST", "kill", new StopProcessUseCase())) } } diff --git a/server/src/main.ts b/server/src/main.ts index a499964..5669550 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -5,10 +5,11 @@ import { extensions } from "./core/extensions/extensions"; import { httpRoutes } from "./core/controllers/routes"; import { executorProgramService } from "./core/usecases/exec_process_usecase"; import { main } from "./p"; +import { executorProgramServiceV2 } from "./core/scenarios/exec_process_scenario_v2"; extensions(); -const socketSubscribers = [new SocketSubscriber(executorProgramService, "realtime")]; +const socketSubscribers = [new SocketSubscriber(executorProgramService, "realtime"), new SocketSubscriber(executorProgramServiceV2, 'realtimeV2',)]; new App(httpRoutes, socketSubscribers).listen(); // main() \ No newline at end of file diff --git a/ui/src/core/repository/core_socket_repository.ts b/ui/src/core/repository/core_socket_repository.ts index db8d3eb..acd0e32 100644 --- a/ui/src/core/repository/core_socket_repository.ts +++ b/ui/src/core/repository/core_socket_repository.ts @@ -1,23 +1,27 @@ import { Socket, io } from "socket.io-client"; import { Result } from "../helper/result"; import { TypedEvent } from "../helper/typed_event"; +import { RuntimeModel } from "../../features/behavior_tree_builder/presentation/ui/actions/runtime_actions"; export class SocketRepository extends TypedEvent { serverURL = "ws://localhost:4001"; socket: Socket | undefined; - - async connect():Promise> { + + async connect(): Promise> { const socket = io(this.serverURL); - + this.socket = socket; socket.connect(); - socket.on('realtime', (d) =>{ + socketCoreInstances.forEach((el) => { + socket.on(el.event, (event) => el.emit(event)) + }) + socket.on('realtime', (d) => { this.emit({ - event:"realtime", - payload:d + event: "realtime", + payload: d }) }) - if(socket.connected){ + if (socket.connected) { return Result.ok(true) } return Result.error(false) @@ -25,4 +29,15 @@ export class SocketRepository extends TypedEvent { } -export const socketRepository = new SocketRepository() \ No newline at end of file +export const socketRepository = new SocketRepository() + + +export abstract class SocketCore extends TypedEvent { + abstract event: string + +} +export class RunTimeSocketRepository extends SocketCore { + event = 'realtimeV2'; +} +export const runTimeSocketRepository = new RunTimeSocketRepository(); +const socketCoreInstances: SocketCore[] = [runTimeSocketRepository] \ No newline at end of file diff --git a/ui/src/core/store/base_store.ts b/ui/src/core/store/base_store.ts index ebd7e65..be176e8 100644 --- a/ui/src/core/store/base_store.ts +++ b/ui/src/core/store/base_store.ts @@ -17,6 +17,7 @@ interface IMessage { errorMessage?: string; } export abstract class UiLoader { + navigate?: NavigateFunction; isLoading = false; async httpHelper(callBack: Promise>) { this.isLoading = true; @@ -75,7 +76,7 @@ export abstract class UiErrorState extends UiLoader { console.log(error); }; abstract init(navigate?: NavigateFunction): Promise; - dispose() {} + dispose() { } errors: UiBaseError[] = []; } diff --git a/ui/src/core/ui/icons/icons.tsx b/ui/src/core/ui/icons/icons.tsx index 33b571b..823e680 100644 --- a/ui/src/core/ui/icons/icons.tsx +++ b/ui/src/core/ui/icons/icons.tsx @@ -931,6 +931,18 @@ const getIconSvg = ( /> ); + + case "3points": + return Result.ok( + + + + ); case "Move": return Result.ok( diff --git a/ui/src/core/ui/pages/main_page_v2.tsx b/ui/src/core/ui/pages/main_page_v2.tsx index 8ff7c70..b4275f8 100644 --- a/ui/src/core/ui/pages/main_page_v2.tsx +++ b/ui/src/core/ui/pages/main_page_v2.tsx @@ -107,7 +107,7 @@ export const MainPageV2: React.FC<{ overflow: "auto", }} > -
{rightChild}
- + */}
{children}
diff --git a/ui/src/core/ui/popover/popover.tsx b/ui/src/core/ui/popover/popover.tsx new file mode 100644 index 0000000..55b6901 --- /dev/null +++ b/ui/src/core/ui/popover/popover.tsx @@ -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 = ({ content, children }) => { + const [visible, setVisible] = useState(false); + const ref = useRef(null); + const togglePopover = () => { + setVisible((prev) => !prev); + }; + + return ( +
togglePopover()} + > + {children} +
+ {content} +
+
+ ); +}; +// visibility: ${({ visible }) => (visible ? 'visible' : 'hidden')}; +// opacity: ${({ visible }) => (visible ? 1 : 0)}; +// transition: opacity 0.2s ease, visibility 0.2s ease; +export default PopoverV2; diff --git a/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_screen.tsx b/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_screen.tsx index 4f401b2..4a746f8 100644 --- a/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_screen.tsx +++ b/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_screen.tsx @@ -4,20 +4,15 @@ import { createEditor } from "./ui/editor/editor"; import { SkillTree } from "./ui/skill_tree/skill_tree"; import { BehaviorTreeBuilderStore, DrawerState } from "./behavior_tree_builder_store"; import { observer } from "mobx-react-lite"; -import { match } from "ts-pattern"; import { Icon } from "../../../core/ui/icons/icons"; import { CoreText, CoreTextType } from "../../../core/ui/text/text"; import { useNavigate, useParams } from "react-router-dom"; 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 { 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 { DrawerV2 } from "../../../core/ui/drawer/drawer"; import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"; +import PopoverV2 from "../../../core/ui/popover/popover"; export const behaviorTreeBuilderScreenPath = "behavior/tree/screen/path"; @@ -66,54 +61,36 @@ export const BehaviorTreeBuilderScreen = observer(() => { }, []); return ( -
- {/* {}} text="Запуск" textColor={themeStore.theme.black} /> -
- {}} - text="Стоп" - type={ButtonV2Type.empty} - textColor={themeStore.theme.greenWhite} - /> -
*/} - {store.isNeedSaveBtn ? ( -
- {store.isNeedSaveBtn ? ( - store.onClickSaveBehaviorTree()} type="Floppy" /> - ) : undefined} -
- ) : ( - <> - )} -
- - } style={{ position: "absolute", height: "100%", overflow: "hidden" }} bgColor={themeStore.theme.black} children={ <> <> +
+
+
+ store.onClickSaveBehaviorTree()} type="Floppy" /> +
+
+
{ width: "calc(100% - 300px)", }} > - {/* +
{ }} /> - {store.panels.map((el) => ( + {store.panels.map((el, index) => ( <> - -
BANE
+ +
+ + } + >
+ + store.panelResize(size, index)} + >
-
- + style={{ + display: "flex", + width: "100%", + justifyContent: "space-between", + backgroundColor: "beige", + }} + > + } + content={ +
+ {store.panelActions.map((el, i) => ( + el.action(index)} /> + ))} +
+ } + /> +
{el.name}
+
+ {el.body} +
))} -
*/} +
diff --git a/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_store.tsx b/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_store.tsx index e1fdc7c..83c90fc 100644 --- a/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_store.tsx +++ b/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_store.tsx @@ -26,6 +26,7 @@ import { BehaviorTreeModel } from "../model/behavior_tree_model"; import { PrimitiveViewModel, SystemPrimitive } from "../model/primitive_view_model"; import { SceneAsset } from "../../../core/model/scene_asset"; import { themeStore } from "../../.."; +import { RunTimeActions } from "./ui/actions/runtime_actions"; interface I2DArea { x: number; @@ -37,8 +38,31 @@ interface I2DArea { export enum DrawerState { 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 { + panelActions: IActionPanel[] = [ + { name: "Добавить панель", action: () => this.addNewPanel() }, + { name: "Убрать панель", action: (index) => this.removePanel(index) }, + { name: "Runtime", action: (index) => this.changePanel(index, "Runtime", ) }, + ]; sceneAsset?: SceneAsset; viewModel: BehaviorTreeViewModel = BehaviorTreeViewModel.empty(); behaviorTreeModel: BehaviorTreeModel = BehaviorTreeModel.empty(); @@ -51,11 +75,10 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState; areaPlugin?: AreaPlugin; nodeUpdateObserver?: NodeRerenderObserver; @@ -70,8 +93,12 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState, "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() { super(DrawerState); makeAutoObservable(this); @@ -116,7 +143,7 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState { @@ -346,4 +373,11 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState {}; + 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); + }; } diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/actions/runtime_actions.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/actions/runtime_actions.tsx new file mode 100644 index 0000000..ea5ecde --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/actions/runtime_actions.tsx @@ -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(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 { + 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 { + 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 ( +
+ {store.isLoading ? ( + + ) : ( + <> + {store.isExecuteBt() ? ( + <> +
store.executeBt()} + > + Дерево запущено +
+ + ) : ( + <> +
store.executeBt()} + > + Запустить дерево +
+ + )} + + )} +
+ ); +}); diff --git a/ui/src/features/behavior_tree_manager/behavior_tree_manager_store.ts b/ui/src/features/behavior_tree_manager/behavior_tree_manager_store.ts index f19fc5b..4d2b069 100644 --- a/ui/src/features/behavior_tree_manager/behavior_tree_manager_store.ts +++ b/ui/src/features/behavior_tree_manager/behavior_tree_manager_store.ts @@ -12,7 +12,6 @@ export enum DrawerState { } export class BehaviorTreeManagerStore extends UiDrawerFormState { viewModel: BehaviorTreeViewModel = BehaviorTreeViewModel.empty(); - navigate?: NavigateFunction; btTreeModels: BehaviorTreeModel[] = []; scenes?: SceneModel[]; behaviorTreeManagerHttpRepository = new BehaviorTreeManagerHttpRepository(); diff --git a/ui/src/features/calculation_instance/presentation/ui/cards/pose_estimate_card/model_card.tsx b/ui/src/features/calculation_instance/presentation/ui/cards/pose_estimate_card/model_card.tsx index f619f93..079cd8c 100644 --- a/ui/src/features/calculation_instance/presentation/ui/cards/pose_estimate_card/model_card.tsx +++ b/ui/src/features/calculation_instance/presentation/ui/cards/pose_estimate_card/model_card.tsx @@ -26,7 +26,6 @@ export class PoseEstimateRepository extends HttpRepository { execAnalyze = (id: string) => this._jsonRequest(HttpMethod.POST, `/run_time/exec/analyze?id=${id}`); } export class PoseEstimateStore extends UiErrorState { - navigate?: NavigateFunction; poseEstimateRepository = new PoseEstimateRepository(); constructor() { super();