Source: backend/backend.js

  1. import { Vector3, Box3 } from "../geometry.js";
  2. import { EntityRef, NodeRef, EdgeRef, DirEdgeRef } from "./entity.js";
  3. import { HookContainer } from "../hook_container.js";
  4. import { asyncFrom } from "../utils.js";
  5. import { NodeTypeRegistry } from "../node_type.js";
  6. import { LayerRegistry } from "../layer.js";
  7. /** Abstract mapper backend, i.e. what map is being presented.
  8. * The backend translates between the concept of a map and a database, a file, an API, or whatever else is actually being used to store the data.
  9. * Most methods here are low-level; users of the backend should use methods from EntityRef and its children which delegate to the MapBackend.
  10. *
  11. * Underlying structure:
  12. * The backend consists of a set of entities, which can have arbitrary properties.
  13. * A special entity, "global", is used for properties of the whole map.
  14. * Two types of entities have specific handling to form a graph:
  15. * "node" - an entity with a parent and positional information.
  16. * "edge" - an entity connecting two adjacent nodes.
  17. */
  18. class MapBackend {
  19. constructor() {
  20. this.loaded = false;
  21. this.hooks = new HookContainer();
  22. this.nodeTypeRegistry = new NodeTypeRegistry();
  23. this.layerRegistry = new LayerRegistry();
  24. this.entityCache = {};
  25. }
  26. getEntityCache(id) {
  27. let cache = this.entityCache[id];
  28. if(cache === undefined) {
  29. cache = this.entityCache[id] = {
  30. properties: {},
  31. };
  32. }
  33. return cache;
  34. }
  35. /** Get the database version number. Implementation defined.
  36. * @returns {number}
  37. */
  38. getVersionNumber() {
  39. throw "getVersionNumber not defined";
  40. }
  41. /** Get the latest backend version number. Implementation defined. Must be greater than zero.
  42. * @returns {number}
  43. */
  44. getBackendVersionNumber() {
  45. throw "getBackendVersionNumber not defined";
  46. }
  47. /** Get a number property on an entity.
  48. * Has a default implementation based on string properties.
  49. * @returns {number}
  50. */
  51. async getPNumber(entityId, propertyName) {
  52. return parseFloat(await this.getPString(entityId, propertyName));
  53. }
  54. /** Set a number property on an entity.
  55. * Has a default implementation based on string properties.
  56. */
  57. async setPNumber(entityId, propertyName, value) {
  58. return this.setPString(entityId, propertyName, value.toString());
  59. }
  60. /** Set a Vector3 property on an entity.
  61. * Has a default implementation based on string properties.
  62. */
  63. async setPVector3(entityId, propertyName, v) {
  64. return this.setPString(entityId, propertyName, JSON.stringify(v));
  65. }
  66. /** Get a Vector3 property on an entity.
  67. * Has a default implementation based on string properties.
  68. * @returns {Vector3}
  69. */
  70. async getPVector3(entityId, propertyName) {
  71. const object = JSON.parse(await this.getPString(entityId, propertyName));
  72. return Vector3.fromJSON(object.x, object.y, object.z);
  73. }
  74. /** Get a string property on an entity.
  75. * @returns {string}
  76. */
  77. async getPString(entityId, propertyName) {
  78. entityId;
  79. propertyName;
  80. throw "getPString not implemented";
  81. }
  82. /** Set a string property on an entity. */
  83. async setPString(entityId, propertyName, value) {
  84. entityId;
  85. propertyName;
  86. value;
  87. throw "setPString not implemented";
  88. }
  89. /** Create a new entity in the backend.
  90. * @param type {string} Type of the new entity.
  91. * @returns {EntityRef}
  92. */
  93. async createEntity(type) {
  94. type;
  95. throw "createEntity not implemented";
  96. }
  97. /** Creates a new "node" entity.
  98. * @param parentId {number|undefined} ID of the parent node, or undefined if the node has no parent.
  99. * @param nodeType {string} Type of the node. "object" or "point".
  100. * @returns {NodeRef}
  101. */
  102. async createNode(parentId, nodeType) {
  103. parentId;
  104. nodeType;
  105. throw "createNode not implemented";
  106. }
  107. /** Get the parent node of a node by ID, or null if the node has no parent.
  108. * @returns {NodeRef|null}
  109. */
  110. async getNodeParent(nodeId) {
  111. nodeId;
  112. throw "getNodeParent not implemented";
  113. }
  114. async setNodeParent(nodeId, parentId) {
  115. nodeId;
  116. parentId;
  117. throw "setNodeParent not implemented";
  118. }
  119. /** Get all direct children of a node.
  120. * @returns {AsyncIterable.<NodeRef>}
  121. */
  122. async getNodeChildren(nodeId) {
  123. nodeId;
  124. throw "getNodeChildren not implemented";
  125. }
  126. /** Check if a node has any children.
  127. * Has a default implementation based on #getNodeChildren().
  128. * @returns {boolean}
  129. */
  130. async nodeHasChildren(nodeId) {
  131. for await(const child of this.getNodeChildren(nodeId)) {
  132. child;
  133. return true;
  134. }
  135. return false;
  136. }
  137. /** Get a node's type.
  138. * @returns {string}
  139. */
  140. async getNodeType(nodeId) {
  141. nodeId;
  142. throw "getNodeType not implemented";
  143. }
  144. /** Create a new edge between two nodes.
  145. * Order of node IDs does not matter.
  146. * @param nodeAId {number} The ID of one of the nodes on the edge.
  147. * @param nodeBId {number} The ID of the other node on the edge.
  148. * @returns {EdgeRef} A reference to the new edge.
  149. */
  150. async createEdge(nodeAId, nodeBId) {
  151. nodeAId;
  152. nodeBId;
  153. throw "createEdge not implemented";
  154. }
  155. /** Get all edges attached to a node.
  156. * @returns {AsyncIterable.<EdgeRef>}
  157. */
  158. async getNodeEdges(nodeId) {
  159. nodeId;
  160. throw "getNodeEdges not implemented";
  161. }
  162. /** Get the two nodes attached to an edge, in no particular order.
  163. * @returns {AsyncIterable.<NodeRef>}
  164. */
  165. async getEdgeNodes(edgeId) {
  166. edgeId;
  167. throw "getEdgeNodes not implemented";
  168. }
  169. /** Given an edge and one of the nodes on the edge, get the other node on the edge.
  170. * @param edgeId {number}
  171. * @param nodeId {number}
  172. * Has a default implementation based on #getEdgeNodes().
  173. * @returns {NodeRef}
  174. */
  175. async getEdgeOtherNode(edgeId, nodeId) {
  176. const [nodeA, nodeB] = await asyncFrom(this.getEdgeNodes(edgeId));
  177. return (nodeA.id == nodeId) ? nodeB : nodeA;
  178. }
  179. /** Remove an edge from the backend.
  180. * Has a default implementation that just removes the entity.
  181. */
  182. async removeEdge(edgeId) {
  183. return this.removeEntity(edgeId);
  184. }
  185. /** Remove a node from the backend.
  186. * Has a default implementation that just removes the entity.
  187. */
  188. async removeNode(nodeId) {
  189. return this.removeEntity(nodeId);
  190. }
  191. /** Check if an entity exists.
  192. * @returns {boolean}
  193. */
  194. async entityExists(entityId) {
  195. entityId;
  196. throw "entityExists not implemented";
  197. }
  198. /** Remove an entity from the backend.
  199. * This method should work to remove any entity.
  200. * However, calling code should use #removeEdge() and #removeNode() when applicable instead, for potential optimization purposes.
  201. */
  202. async removeEntity(entityId) {
  203. entityId;
  204. throw "removeEntity not implemented";
  205. }
  206. /** Flush the backend to storage.
  207. * This may happen automatically, but flush forces it.
  208. * Has a default implementation that does nothing.
  209. */
  210. async flush() {
  211. }
  212. /** Create an EntityRef to an entity in this backend.
  213. * Use getNodeRef, getEdgeRef, or getDirEdgeRef for greater type-specific functionality if the entity is a node or edge.
  214. */
  215. getEntityRef(id) {
  216. return new EntityRef(id, this);
  217. }
  218. /** Create a NodeRef to a node in this backend. */
  219. getNodeRef(id) {
  220. return new NodeRef(id, this);
  221. }
  222. /** Create an EdgeRef to an edge in this backend. */
  223. getEdgeRef(id) {
  224. return new EdgeRef(id, this);
  225. }
  226. /** Create a DirEdgeRef to an edge in this backend, starting from the specified node.
  227. * @param id {number} The ID of the edge to get.
  228. * @param startId {number} The ID of a node attached to this edge.
  229. * @returns {DirEdgeRef} Starting from the specified start ID.
  230. */
  231. getDirEdgeRef(id, startId) {
  232. return new DirEdgeRef(id, startId, this);
  233. }
  234. /** Get all nodes within a spatial box.
  235. * @param box {Box3} The box to find nodes within.
  236. * @returns {AsyncIterable.<NodeRef>}
  237. */
  238. getNodesInArea(box) {
  239. box;
  240. throw "getNodesInArea not implemented";
  241. }
  242. /** Get all nodes in or near a spatial box (according to their radii).
  243. * @param box {Box3} The box to find nodes within or near.
  244. * @param minRadius {number} The minimum radius of nodes to return.
  245. * @returns {AsyncIterable.<NodeRef>}
  246. */
  247. getObjectNodesTouchingArea(box, minRadius) {
  248. box;
  249. minRadius;
  250. throw "getObjectNodesTouchingArea not implemented";
  251. }
  252. /** Get the edge between two nodes, if it exists.
  253. * @param nodeAId {number} The ID of one of the nodes on the edge to find.
  254. * @param nodeBId {number} The ID of the other node on the edge to find.
  255. * @returns {EdgeRef}
  256. */
  257. getEdgeBetween(nodeAId, nodeBId) {
  258. nodeAId;
  259. nodeBId;
  260. throw "getEdgeBetween not implemented";
  261. }
  262. /** Get all nearby nodes within a specified blend distance of the specified node.
  263. * Has a default implementation based on #getNodesInArea().
  264. * @param nodeRef {NodeRef} The node that is the spatial center of the search.
  265. * @param blendDistance {number} How far out to look for nodes? (Necessary to avoid searching the entire map.)
  266. * @returns {AsyncIterable.<NodeRef>} All the discovered nodes. Does not include the original node.
  267. */
  268. async * getNearbyNodes(nodeRef, blendDistance) {
  269. for await (const otherNodeRef of this.getNodesInArea(Box3.fromRadius(await nodeRef.getCenter(), blendDistance))) {
  270. if(otherNodeRef.id !== nodeRef.id) {
  271. yield otherNodeRef;
  272. }
  273. }
  274. }
  275. /** Get all nodes connected to the specified node by one level of edges (that is, one edge).
  276. * Has a default implementation based on #getNodeEdges().
  277. * @param nodeRef {NodeRef} The node to search for connections on.
  278. * @returns {AsyncIterable.<NodeRef>} The connected nodes.
  279. */
  280. async * getConnectedNodes(nodeRef) {
  281. for await (const dirEdgeRef of this.getNodeEdges(nodeRef.id)) {
  282. yield await dirEdgeRef.getDirOtherNode();
  283. }
  284. }
  285. /** Get all edges within a specified blend distance that intersect with the given edge.
  286. * Has a default implementation based on #getNodesInArea() and #NodeRef.getEdges().
  287. * @param edgeRef {EdgeRef} The edge to search for intersections on.
  288. * @param blendDistance {number} How far out to search for intersections? (Necessary to avoid searching the entire map.)
  289. * @returns {AsyncIterable.<EdgeRef>} Each intersecting edge found.
  290. */
  291. async * getIntersectingEdges(edgeRef, blendDistance) {
  292. const line = await edgeRef.getLine();
  293. const distance = Vector3.UNIT.multiplyScalar(blendDistance);
  294. const seen = {
  295. [edgeRef.id]: true,
  296. };
  297. for await (const nodeRef of this.getNodesInArea(new Box3(line.fullMin().subtract(distance), line.fullMax().add(distance)))) {
  298. for await (const dirEdgeRef of nodeRef.getEdges()) {
  299. if(!seen[dirEdgeRef.id]) {
  300. seen[dirEdgeRef.id] = true;
  301. if(line.intersects2(await dirEdgeRef.getLine())) {
  302. yield dirEdgeRef;
  303. }
  304. }
  305. }
  306. }
  307. }
  308. }
  309. export { MapBackend };