webgl test and class validator mocker

This commit is contained in:
IDONTSUDO 2023-12-19 11:54:47 +03:00
parent b9a89a4ba7
commit 3ff2186deb
17 changed files with 368 additions and 72 deletions

View file

@ -15,6 +15,6 @@
"*ui": false,
"*ui.*": false
},
"cSpell.words": ["antd", "fileupload", "uuidv"],
"cSpell.words": ["antd", "fileupload", "metadatas", "uuidv"],
"editor.rulers": [100]
}

View file

@ -76,7 +76,9 @@ export class App {
}
async loadAppDependencies() {
await new DataBaseConnectUseCase().call();
if ((await new DataBaseConnectUseCase().call()).isFailure()) {
console.log("database connect error");
}
await new CheckAndCreateStaticFilesFolderUseCase().call();
await new SetLastActivePipelineToRealTimeServiceScenario().call();
}

View file

@ -0,0 +1,82 @@
import { randomBytes, randomInt, randomUUID } from "crypto";
import { getMetadataStorage, IS_BOOLEAN, IS_MONGO_ID, IS_NUMBER, IS_STRING, IS_UUID } from "class-validator";
import { ValidationMetadata } from "class-validator/types/metadata/ValidationMetadata";
type AvailableTypes = string | number | boolean | undefined;
export class ClassValidatorMocker {
// eslint-disable-next-line @typescript-eslint/ban-types
public static create<T>(constructor: Function, partial: Partial<T> = {}): T {
return new ClassValidatorMocker().create(constructor, partial);
}
// eslint-disable-next-line @typescript-eslint/ban-types
public create<T>(constructor: Function, partial: Partial<T> = {}): T {
const metadataStorage = getMetadataStorage();
const targetMetadatas = metadataStorage.getTargetValidationMetadatas(constructor, "", false, false);
const groupedMetadatas = metadataStorage.groupByPropertyName(targetMetadatas);
// nestedValidation
console.log(targetMetadatas);
let randomFixture = {} as T;
for (const propertyName of Object.keys(groupedMetadatas)) {
const metadatas = groupedMetadatas[propertyName];
const value = this.generatePropertyValueFromMetadatas(metadatas);
if (value !== undefined) {
randomFixture = {
...randomFixture,
[propertyName]: value,
};
}
}
return { ...randomFixture, ...partial };
}
private generatePropertyValueFromMetadatas(metadatas: ValidationMetadata[]): AvailableTypes {
for (const metadata of metadatas) {
const constraints = getMetadataStorage().getTargetValidatorConstraints(metadata.constraintCls);
for (const constraint of constraints) {
switch (constraint.name) {
case IS_MONGO_ID:
return this.randomUUID();
case IS_STRING:
return this.randomString();
case IS_NUMBER:
return this.randomNumber();
case IS_BOOLEAN:
return this.randomBoolean();
case IS_UUID:
return this.randomUUID();
default:
break;
}
}
}
return undefined;
}
private randomString(): string {
return randomBytes(randomInt(1, 10)).toString("hex");
}
private randomNumber(): number {
return randomInt(0, 99_999);
}
private randomBoolean(): boolean {
return randomInt(0, 1) === 1;
}
private randomUUID(): string {
if (randomUUID != null) {
return randomUUID();
}
return randomBytes(16).toString("hex");
}
}

View file

@ -2,7 +2,7 @@ import { IsOptional, ValidateNested } from "class-validator";
import { IPipeline, IProcess, StackGenerateType } from "../../../core/models/process_model";
import { Type } from "class-transformer";
import { ProcessModel } from "../../process/models/process_validation_model";
import { TriggerModel } from "../../triggers/models/trigger_validation_model";
import { TriggerModelValidationModel } from "../../triggers/models/trigger_validation_model";
export class PipelineModel implements IPipeline {
@ValidateNested()
@ -10,8 +10,8 @@ export class PipelineModel implements IPipeline {
public process: IProcess;
@ValidateNested()
@Type(() => TriggerModel)
public trigger: TriggerModel;
@Type(() => TriggerModelValidationModel)
public trigger: TriggerModelValidationModel;
@IsOptional()
public env = null;

View file

@ -1,13 +1,13 @@
import { IsMongoId, IsOptional } from "class-validator";
import { IProcess, StackGenerateType } from "../../../core/models/process_model";
import { TriggerModel } from "../../triggers/models/trigger_validation_model";
import { TriggerModelValidationModel } from "../../triggers/models/trigger_validation_model";
export class PipelineValidationModel {
@IsMongoId()
public process: IProcess;
@IsMongoId()
public trigger: TriggerModel;
public trigger: TriggerModelValidationModel;
@IsOptional()
public env = null;

View file

@ -1,7 +1,7 @@
import { IsArray, IsOptional, IsEnum, IsString } from "class-validator";
import { ITriggerModel, TriggerType } from "./trigger_database_model";
export class TriggerModel implements ITriggerModel {
export class TriggerModelValidationModel implements ITriggerModel {
@IsOptional()
public _id: string;

View file

@ -1,12 +1,12 @@
import { CrudController } from "../../core/controllers/crud_controller";
import { TriggerDBModel } from "./models/trigger_database_model";
import { TriggerModel } from "./models/trigger_validation_model";
import { TriggerModelValidationModel as TriggerValidationMode } from "./models/trigger_validation_model";
export class TriggerPresentation extends CrudController<TriggerModel, typeof TriggerDBModel> {
export class TriggerPresentation extends CrudController<TriggerValidationMode, typeof TriggerDBModel> {
constructor() {
super({
url: "trigger",
validationModel: TriggerModel,
validationModel: TriggerValidationMode,
databaseModel: TriggerDBModel,
});
}

View file

@ -9,11 +9,11 @@ import { ProcessPresentation } from "./features/process/process_presentation";
import { RealTimePresentation, pipelineRealTimeService } from "./features/realtime/realtime_presentation";
import { extensions } from "./core/extensions/extensions";
import { ProjectInstancePresentation } from "./features/project_instance/project_instance_presentation";
import { NixStoreManagerPresentation as NixStoreManagerPresentation } from "./features/nix_store_manager/nix_store_manager";
import { NixStoreManagerPresentation } from "./features/nix_store_manager/nix_store_manager";
extensions();
const httpRoutes: Routes[] = [
export const httpRoutes: Routes[] = [
new TriggerPresentation(),
new ProjectsPresentation(),
new ProcessPresentation(),

View file

@ -0,0 +1,25 @@
import { Type } from "class-transformer";
import { ClassValidatorMocker } from "../../src/core/helpers/class_validator_mocket";
import { IsString, IsNumber, IsBoolean, IsUUID, IsMongoId, ValidateNested } from "class-validator";
class Foo {}
class MyClass {
@ValidateNested()
@Type(() => Foo)
model: Foo;
@IsNumber()
numberProperty: number;
@IsBoolean()
booleanProperty: boolean;
@IsUUID()
uuidProperty: string;
}
const myClassDataMock = ClassValidatorMocker.create<MyClass>(MyClass);
export const mainTest = () => {
console.log(myClassDataMock);
};

View file

@ -1,3 +1,4 @@
import "reflect-metadata";
import { TestCore } from "./core/test_core";
// import { UnitTestEnv } from "../src/core/di/env";
import { dirname } from "path";
@ -13,6 +14,7 @@ import { UpdateDataBaseModelUseCaseTest } from "./usecases/update_database_model
import { PaginationDataBaseModelUseCaseTest } from "./usecases/pagination_database_model_usecase_test";
import { extensions } from "../src/core/extensions/extensions";
import { DataBaseConnectUseCase } from "../src/core/usecases/database_connect_usecase";
import { mainTest } from "./helper/class_validator_mocker_test";
extensions();
@ -34,20 +36,22 @@ const init = async () => {
};
const test = async () => {
await new ExecutorProgramServiceTest(dirname__).test();
await new FilesChangerTest(dirname__).test();
await new StackServiceTest(dirname__ + "/context/").test();
await new TriggerServiceTest().test();
await new CreateDataBaseModelUseCaseTest().test();
// await new ExecutorProgramServiceTest(dirname__).test();
// await new FilesChangerTest(dirname__).test();
// await new StackServiceTest(dirname__ + "/context/").test();
// await new TriggerServiceTest().test();
// await new CreateDataBaseModelUseCaseTest().test();
await new CreateDataBaseModelUseCaseTest().test();
await new DeleteDataBaseModelUseCaseTest().test();
await new ReadDataBaseModelUseCaseTest().test();
await new UpdateDataBaseModelUseCaseTest().test();
// await new PipelineRealTimeServiceTest().test()
for await (const usecase of tests) {
testCore.assert(await new usecase().test(), usecase.name);
}
// await new CreateDataBaseModelUseCaseTest().test();
// await new DeleteDataBaseModelUseCaseTest().test();
// await new ReadDataBaseModelUseCaseTest().test();
// await new UpdateDataBaseModelUseCaseTest().test();
// // await new PipelineRealTimeServiceTest().test()
// for await (const usecase of tests) {
// testCore.assert(await new usecase().test(), usecase.name);
// }
mainTest();
};
const main = async () => {
await init();

View file

@ -29,7 +29,7 @@
"three": "^0.159.0",
"typescript": "^4.9.5",
"urdf-loader": "^0.12.1",
"uuid": "^9.0.0",
"uuid": "^9.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {

View file

@ -0,0 +1,42 @@
export interface Listener<T> {
(event: T): any;
}
export interface Disposable {
dispose(): void;
}
export class TypedEvent<T> {
private listeners: Listener<T>[] = [];
public listenersOnces: Listener<T>[] = [];
on = (listener: Listener<T>): Disposable => {
this.listeners.push(listener);
return {
dispose: () => this.off(listener),
};
};
once = (listener: Listener<T>): void => {
this.listenersOnces.push(listener);
};
off = (listener: Listener<T>) => {
const callbackIndex = this.listeners.indexOf(listener);
if (callbackIndex > -1) this.listeners.splice(callbackIndex, 1);
};
emit = (event: T) => {
this.listeners.forEach((listener) => listener(event));
if (this.listenersOnces.length > 0) {
const toCall = this.listenersOnces;
this.listenersOnces = [];
toCall.forEach((listener) => listener(event));
}
};
pipe = (te: TypedEvent<T>): Disposable => {
return this.on((e) => te.emit(e));
};
}

View file

@ -12,15 +12,29 @@ import {
Object3DEventMap,
Box3,
Sphere,
LineBasicMaterial,
EdgesGeometry,
Raycaster,
LineSegments,
Vector2,
} from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { BaseSceneItemModel, StaticAssetItemModel } from "../../features/scene_manager/scene_manager_store";
import { TypedEvent } from "../helper/typed_event";
import { Result } from "../helper/result";
export class CoreThereRepository {
interface IEmissiveCache {
status: boolean;
object3d: Object3D<Object3DEventMap>;
}
export class CoreThereRepository extends TypedEvent<BaseSceneItemModel> {
scene = new Scene();
camera: PerspectiveCamera;
webGlRender: WebGLRenderer;
htmlCanvasRef: HTMLCanvasElement;
objectEmissive = new Map<string, IEmissiveCache>();
constructor(htmlCanvasRef: HTMLCanvasElement) {
super();
const renderer = new WebGLRenderer({
canvas: htmlCanvasRef as HTMLCanvasElement,
antialias: true,
@ -32,14 +46,22 @@ export class CoreThereRepository {
this.htmlCanvasRef = htmlCanvasRef;
this.init();
}
setRayCastAndGetFirstObject(vector: Vector2): Result<void, string> {
const raycaster = new Raycaster();
raycaster.setFromCamera(vector, this.camera);
const intersects = raycaster.intersectObjects(this.scene.children);
if (intersects.length > 0) {
return Result.ok(intersects[0].object.name);
}
return Result.error(undefined);
}
addCube(num: number, translateTo: string = "X") {
const geometry = new BoxGeometry(1, 1, 1);
const material = new MeshBasicMaterial({ color: 0x00ff00 });
const cube = new Mesh(geometry, material);
cube.name = "Cube" + String(num);
// cube.translateX(position.x);
// cube.translateY(position.y);
// cube.translateZ(position.z);
eval(`cube.translate${translateTo}(${num * 10})`);
this.scene.add(cube);
}
@ -70,6 +92,9 @@ export class CoreThereRepository {
this.webGlRender.render(this.scene, this.camera);
});
}
getAllSceneModels(): BaseSceneItemModel[] {
return this.getAllSceneNameModels().map((e) => new StaticAssetItemModel(e));
}
getAllSceneNameModels(): string[] {
return this.scene.children.filter((el) => el.name !== "").map((el) => el.name);
}
@ -106,36 +131,55 @@ export class CoreThereRepository {
orbitControls.maxDistance = cameraToFarEdge * 2;
new OrbitControls(this.camera, this.htmlCanvasRef);
}
switchObjectEmissive(name: string) {
const mesh = this.getObjectsAtName(name);
const result = this.objectEmissive.get(mesh.name);
if (result?.status) {
this.scene.remove(mesh);
this.scene.add(result.object3d);
this.objectEmissive.set(mesh.name, {
status: false,
object3d: mesh,
});
} else {
this.objectEmissive.set(mesh.name, {
status: true,
object3d: mesh,
});
if (mesh instanceof Mesh) {
const newMesh = new LineSegments(mesh.geometry, new LineBasicMaterial({ color: 0x000000 }));
newMesh.name = mesh.name;
newMesh.translateX(mesh.position.x);
newMesh.translateY(mesh.position.y);
newMesh.translateZ(mesh.position.z);
this.scene.remove(mesh);
this.scene.add(newMesh);
}
}
}
fitSelectedObjectToScreen(objects: string[]) {
//https://stackoverflow.com/questions/14614252/how-to-fit-camera-to-object
let boundBox = new Box3().setFromPoints(objects.map((el) => this.getObjectsAtName(el)).map((el) => el.position));
let boundSphere = boundBox.getBoundingSphere(new Sphere());
let vFoV = this.camera.getEffectiveFOV();
let hFoV = this.camera.fov * this.camera.aspect;
let FoV = Math.min(vFoV, hFoV);
let FoV2 = FoV / 2;
let dir = new Vector3();
this.camera.getWorldDirection(dir);
let bsWorld = boundSphere.center.clone();
let th = (FoV2 * Math.PI) / 180.0;
let sina = Math.sin(th);
let R = boundSphere.radius;
let FL = R / sina;
let cameraDir = new Vector3();
let cameraOffs = cameraDir.clone();
console.log(cameraOffs);
cameraOffs.multiplyScalar(-FL);
console.log(-FL);
let newCameraPos = bsWorld.clone().add(cameraOffs);
console.log(newCameraPos);
this.camera.translateX(newCameraPos.x);
this.camera.translateY(newCameraPos.y);
this.camera.translateZ(newCameraPos.z);

View file

@ -0,0 +1,14 @@
import * as React from "react";
import { StaticAssetItemModel } from "../scene_manager_store";
export interface IStaticAssetModelViewProps {
model: StaticAssetItemModel;
}
export function StaticAssetModelView(props: IStaticAssetModelViewProps) {
return (
<div style={{ width: "100px", textAlignLast: "center", backgroundColor: "aqua", margin: "10px" }}>
{props.model.name}
</div>
);
}

View file

@ -1,43 +1,55 @@
import * as React from "react";
import { CoreThereRepository } from "../../core/repository/core_there_repository";
import { SceneMangerStore, StaticAssetItemModel } from "./scene_manager_store";
import { observer } from "mobx-react-lite";
import { StaticAssetModelView } from "./components/static_asset_item_view";
const useKeyLister = (fn: Function) => {
const pressed = new Map();
// const useKeyLister = (fn: Function) => {
// const pressed = new Map();
const registerKeyPress = React.useCallback(
(event: KeyboardEvent, codes: string[], callBack: Function) => {
if (codes.hasIncludeElement(event.code)) {
pressed.addValueOrMakeCallback(event.code, event.type, (e) => {
if (Array.from(pressed.values()).equals(["keydown", "keydown"], false)) {
callBack();
}
});
}
},
[pressed]
);
// const registerKeyPress = React.useCallback(
// (event: KeyboardEvent, codes: string[], callBack: Function) => {
// if (codes.hasIncludeElement(event.code)) {
// pressed.addValueOrMakeCallback(event.code, event.type, (e) => {
// if (Array.from(pressed.values()).equals(["keydown", "keydown"], false)) {
// callBack();
// }
// });
// }
// },
// [pressed]
// );
React.useEffect(() => {
window.addEventListener("keyup", (e) => registerKeyPress(e, ["KeyQ", "KeyW"], () => fn));
window.addEventListener("keydown", (e) => registerKeyPress(e, ["KeyQ", "KeyW"], () => {}));
}, [fn, registerKeyPress]);
// React.useEffect(() => {
// window.addEventListener("keyup", (e) => registerKeyPress(e, ["KeyQ", "KeyW"], () => fn));
// window.addEventListener("keydown", (e) => registerKeyPress(e, ["KeyQ", "KeyW"], () => {}));
// }, [fn, registerKeyPress]);
return [];
};
export function SceneManger() {
// return [];
// };
export const SceneManger = observer(() => {
const canvasRef = React.useRef<HTMLCanvasElement>(null);
let thereRepository: null | CoreThereRepository = null;
const [sceneMangerStore] = React.useState(() => new SceneMangerStore());
React.useEffect(() => {
// eslint-disable-next-line react-hooks/exhaustive-deps
thereRepository = new CoreThereRepository(canvasRef.current as HTMLCanvasElement);
thereRepository.render();
// thereRepository.fitSelectedObjectToScreen(thereRepository.getAllSceneNameModels());
thereRepository.fitCameraToCenteredObject(thereRepository.getAllSceneNameModels());
sceneMangerStore.loadGl(canvasRef.current!);
return () => {
sceneMangerStore.dispose();
};
});
return (
<div>
<div style={{ position: "absolute" }}>
{sceneMangerStore.sceneItems.map((el) => {
if (el instanceof StaticAssetItemModel) {
return StaticAssetModelView({ model: el });
}
return <></>;
})}
</div>
<canvas ref={canvasRef} />
</div>
);
}
});

View file

@ -0,0 +1,70 @@
/* eslint-disable array-callback-return */
import { makeAutoObservable } from "mobx";
import { CoreThereRepository } from "../../core/repository/core_there_repository";
import { v4 as uuidv4 } from "uuid";
import { Vector2 } from "three";
export class BaseSceneItemModel {
id: string;
constructor() {
this.id = uuidv4();
}
}
enum SceneModelsType {
ASSET,
}
export class StaticAssetItemModel extends BaseSceneItemModel {
name: string;
type = SceneModelsType.ASSET;
constructor(name: string) {
super();
this.name = name;
}
}
export class SceneMangerStore {
coreThereRepository: null | CoreThereRepository = null;
sceneItems: BaseSceneItemModel[] = [];
constructor() {
makeAutoObservable(this);
}
loadGl(canvasRef: HTMLCanvasElement): void {
this.coreThereRepository = new CoreThereRepository(canvasRef as HTMLCanvasElement);
this.coreThereRepository.on(this.watcherThereObjects);
this.coreThereRepository.render();
this.coreThereRepository.fitCameraToCenteredObject(this.coreThereRepository.getAllSceneNameModels());
this.sceneItems = this.coreThereRepository.getAllSceneModels();
window.addEventListener("click", this.handleMouseClick);
}
watcherThereObjects(sceneItemModel: BaseSceneItemModel): void {
this.sceneItems.push(sceneItemModel);
}
handleMouseClick = (event: MouseEvent) => {
const vector = new Vector2();
console.log("====");
console.log(event.pageX);
console.log(event.clientX);
console.log(event.x);
console.log(event.movementX);
console.log(event.screenX);
console.log("====");
vector.x = (event.clientX / window.innerWidth) * 2 - 1;
vector.y = -(event.clientY / window.innerHeight) * 2 + 1;
this.coreThereRepository?.setRayCastAndGetFirstObject(vector).map((el) => {
this.coreThereRepository?.switchObjectEmissive(el);
});
};
dispose() {
window.removeEventListener("click", this.handleMouseClick);
}
}

View file

@ -14,8 +14,9 @@ const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)
root.render(
<>
<SocketLister>
{/* <SocketLister>
<RouterProvider router={router} />
</SocketLister>
</SocketLister> */}
<SceneManger />
</>
);