Huge performance improvement when translating quest entities.

This commit is contained in:
Daan Vanden Bosch 2019-07-20 00:14:51 +02:00
parent f670718637
commit 15327d1478
6 changed files with 811 additions and 304 deletions

View File

@ -7,7 +7,13 @@ export class Vec2 {
this.y = y;
}
add(v: Vec2): Vec2 {
set(x: number, y: number): this {
this.x = x;
this.y = y;
return this;
}
add(v: Vec2): this {
this.x += v.x;
this.y += v.y;
return this;
@ -33,7 +39,14 @@ export class Vec3 {
this.z = z;
}
add(v: Vec3): Vec3 {
set(x: number, y: number, z: number): this {
this.x = x;
this.y = y;
this.z = z;
return this;
}
add(v: Vec3): this {
this.x += v.x;
this.y += v.y;
this.z += v.z;

View File

@ -1,6 +1,6 @@
import { Episode, check_episode } from ".";
import { Episode, check_episode, EntityType } from ".";
export class NpcType {
export class NpcType implements EntityType {
readonly id: number;
/**
* Matches the constant name. E.g. the code of NpcType.Zu is "Zu".

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
import { computed, observable } from "mobx";
import { computed, observable, action } from "mobx";
import { DatNpc, DatObject, DatUnknown } from "../data_formats/parsing/quest/dat";
import { Vec3 } from "../data_formats/vector";
import { enum_values } from "../enums";
@ -127,10 +127,18 @@ export class Quest {
}
}
export interface EntityType {
readonly id: number;
readonly code: string;
readonly name: string;
}
/**
* Abstract class from which QuestNpc and QuestObject derive.
*/
export class QuestEntity {
export class QuestEntity<Type extends EntityType = EntityType> {
readonly type: Type;
@observable area_id: number;
private _section_id: number;
@ -139,14 +147,14 @@ export class QuestEntity {
return this.section ? this.section.id : this._section_id;
}
@observable section?: Section;
@observable.ref section?: Section;
/**
* World position
*/
@observable position: Vec3;
@observable.ref position: Vec3;
@observable rotation: Vec3;
@observable.ref rotation: Vec3;
/**
* Section-relative position
@ -185,9 +193,10 @@ export class QuestEntity {
}
}
constructor(area_id: number, section_id: number, position: Vec3, rotation: Vec3) {
constructor(type: Type, area_id: number, section_id: number, position: Vec3, rotation: Vec3) {
if (Object.getPrototypeOf(this) === Object.getPrototypeOf(QuestEntity))
throw new Error("Abstract class should not be instantiated directly.");
if (!type) throw new Error("type is required.");
if (!Number.isInteger(area_id) || area_id < 0)
throw new Error(`Expected area_id to be a non-negative integer, got ${area_id}.`);
if (!Number.isInteger(section_id) || section_id < 0)
@ -195,14 +204,21 @@ export class QuestEntity {
if (!position) throw new Error("position is required.");
if (!rotation) throw new Error("rotation is required.");
this.type = type;
this.area_id = area_id;
this._section_id = section_id;
this.position = position;
this.rotation = rotation;
}
@action
set_position_and_section(position: Vec3, section?: Section): void {
this.position = position;
this.section = section;
}
}
export class QuestObject extends QuestEntity {
export class QuestObject extends QuestEntity<ObjectType> {
@observable type: ObjectType;
/**
* The raw data from a DAT file.
@ -217,16 +233,14 @@ export class QuestObject extends QuestEntity {
type: ObjectType,
dat: DatObject
) {
super(area_id, section_id, position, rotation);
if (!type) throw new Error("type is required.");
super(type, area_id, section_id, position, rotation);
this.type = type;
this.dat = dat;
}
}
export class QuestNpc extends QuestEntity {
export class QuestNpc extends QuestEntity<NpcType> {
@observable type: NpcType;
/**
* The raw data from a DAT file.
@ -241,7 +255,7 @@ export class QuestNpc extends QuestEntity {
type: NpcType,
dat: DatNpc
) {
super(area_id, section_id, position, rotation);
super(type, area_id, section_id, position, rotation);
if (!type) throw new Error("type is required.");
@ -270,7 +284,7 @@ export class Area {
}
export class AreaVariant {
@observable sections: Section[] = [];
@observable.shallow sections: Section[] = [];
constructor(public id: number, public area: Area) {
if (!Number.isInteger(id) || id < 0)

View File

@ -1,4 +1,4 @@
import { autorun, runInAction } from "mobx";
import { autorun } from "mobx";
import { Intersection, Mesh, MeshLambertMaterial, Plane, Raycaster, Vector2, Vector3 } from "three";
import { Vec3 } from "../data_formats/vector";
import { QuestEntity, QuestNpc, QuestObject, Section } from "../domain";
@ -239,14 +239,14 @@ export class QuestEntityControls {
const { intersection, section } = this.pick_terrain(pointer_position, pick);
if (intersection) {
runInAction(() => {
selection.entity.position = new Vec3(
selection.entity.set_position_and_section(
new Vec3(
intersection.point.x,
intersection.point.y + pick.drag_y,
intersection.point.z
);
selection.entity.section = section;
});
),
section
);
} else {
// If the cursor is not over any terrain, we translate the entity accross the horizontal plane in which the entity's origin lies.
this.raycaster.setFromCamera(pointer_position, this.renderer.camera);

View File

@ -1,8 +1,10 @@
import { InputNumber } from "antd";
import { observer } from "mobx-react";
import React, { ReactNode, Component } from "react";
import React, { ReactNode, Component, PureComponent } from "react";
import { QuestNpc, QuestObject, QuestEntity } from "../../domain";
import "./EntityInfoComponent.css";
import { IReactionDisposer, autorun } from "mobx";
import { Vec3 } from "../../data_formats/vector";
export type Props = {
entity?: QuestEntity;
@ -104,30 +106,71 @@ export class EntityInfoComponent extends Component<Props> {
}
}
@observer
class CoordRow extends Component<{
type CoordProps = {
entity: QuestEntity;
position_type: "position" | "section_position";
coord: "x" | "y" | "z";
}> {
};
class CoordRow extends PureComponent<CoordProps> {
render(): ReactNode {
const entity = this.props.entity;
const value = entity[this.props.position_type][this.props.coord];
return (
<tr>
<td>{this.props.coord.toUpperCase()}: </td>
<td>
<InputNumber
value={value}
size="small"
precision={3}
className="EntityInfoComponent-coord"
onChange={this.changed}
/>
<CoordInput {...this.props} />
</td>
</tr>
);
}
}
class CoordInput extends Component<CoordProps, { value: number }> {
private disposer?: IReactionDisposer;
state = { value: 0 };
componentDidMount(): void {
this.start_observing();
}
componentWillUnmount(): void {
if (this.disposer) this.disposer();
}
componentDidUpdate(prev_props: CoordProps): void {
if (this.props.entity !== prev_props.entity) {
this.start_observing();
}
}
render(): ReactNode {
return (
<InputNumber
value={this.state.value}
size="small"
precision={3}
className="EntityInfoComponent-coord"
onChange={this.changed}
/>
);
}
private start_observing() {
if (this.disposer) this.disposer();
this.disposer = autorun(
() => {
this.setState({
value: this.props.entity[this.props.position_type][this.props.coord],
});
},
{
name: `${this.props.entity.type.code}.${this.props.position_type}.${this.props.coord} changed`,
delay: 50,
}
);
}
private changed = (value?: number) => {
if (value != null) {