Move marker set contents outside the store, other small optimisations.

Improves marker set creation performance by avoiding traversal of all markers when watching the marker set.

Various other small changes to reduce allocations when creating markers
This commit is contained in:
James Lyne 2022-01-11 16:08:30 +00:00
parent 6717cab096
commit ff77f09025
11 changed files with 116 additions and 90 deletions

View File

@ -23,6 +23,7 @@ import LiveAtlasLayerGroup from "@/leaflet/layer/LiveAtlasLayerGroup";
import LiveAtlasPolygon from "@/leaflet/vector/LiveAtlasPolygon";
import LiveAtlasPolyline from "@/leaflet/vector/LiveAtlasPolyline";
import {LiveAtlasArea, LiveAtlasMarkerSet} from "@/index";
import {nonReactiveState} from "@/store/state";
export default defineComponent({
props: {
@ -51,7 +52,7 @@ export default defineComponent({
createAreas = () => {
const converter = currentMap.value!.locationToLatLng.bind(currentMap.value);
props.set.areas.forEach((area: LiveAtlasArea, id: string) => {
nonReactiveState.markers.get(props.set.id)!.areas.forEach((area: LiveAtlasArea, id: string) => {
const layer = createArea(area, converter);
layers.set(id, layer);
@ -103,7 +104,7 @@ export default defineComponent({
if(newValue && (!oldValue || oldValue.world === newValue.world)) {
const converter = newValue.locationToLatLng.bind(newValue);
for (const [id, area] of props.set.areas) {
for (const [id, area] of nonReactiveState.markers.get(props.set.id)!.areas) {
updateArea(layers.get(id), area, converter);
}
}

View File

@ -23,6 +23,7 @@ import LiveAtlasPolyline from "@/leaflet/vector/LiveAtlasPolyline";
import LiveAtlasPolygon from "@/leaflet/vector/LiveAtlasPolygon";
import LiveAtlasLayerGroup from "@/leaflet/layer/LiveAtlasLayerGroup";
import {LiveAtlasCircle, LiveAtlasMarkerSet} from "@/index";
import {nonReactiveState} from "@/store/state";
export default defineComponent({
props: {
@ -51,7 +52,7 @@ export default defineComponent({
createCircles = () => {
const converter = currentMap.value!.locationToLatLng.bind(store.state.currentMap);
props.set.circles.forEach((circle: LiveAtlasCircle, id: string) => {
nonReactiveState.markers.get(props.set.id)!.circles.forEach((circle: LiveAtlasCircle, id: string) => {
const layer = createCircle(circle, converter);
layers.set(id, layer);
@ -103,7 +104,7 @@ export default defineComponent({
if(newValue && (!oldValue || oldValue.world === newValue.world)) {
const converter = currentMap.value!.locationToLatLng.bind(store.state.currentMap);
for (const [id, circle] of props.set.circles) {
for (const [id, circle] of nonReactiveState.markers.get(props.set.id)!.circles) {
updateCircle(layers.get(id), circle, converter);
}
}

View File

@ -22,6 +22,7 @@ import {createLine, updateLine} from "@/util/lines";
import LiveAtlasPolyline from "@/leaflet/vector/LiveAtlasPolyline";
import LiveAtlasLayerGroup from "@/leaflet/layer/LiveAtlasLayerGroup";
import {LiveAtlasLine, LiveAtlasMarkerSet} from "@/index";
import {nonReactiveState} from "@/store/state";
export default defineComponent({
props: {
@ -50,7 +51,7 @@ export default defineComponent({
createLines = () => {
const converter = currentMap.value!.locationToLatLng.bind(store.state.currentMap);
props.set.lines.forEach((line: LiveAtlasLine, id: string) => {
nonReactiveState.markers.get(props.set.id)!.lines.forEach((line: LiveAtlasLine, id: string) => {
const layer = createLine(line, converter);
layers.set(id, layer);
@ -102,7 +103,7 @@ export default defineComponent({
if(newValue && (!oldValue || oldValue.world === newValue.world)) {
const converter = currentMap.value!.locationToLatLng.bind(store.state.currentMap);
for (const [id, line] of props.set.lines) {
for (const [id, line] of nonReactiveState.markers.get(props.set.id)!.lines) {
updateLine(layers.get(id), line, converter);
}
}

View File

@ -22,6 +22,7 @@ import {ActionTypes} from "@/store/action-types";
import {createMarker, updateMarker} from "@/util/markers";
import LiveAtlasLayerGroup from "@/leaflet/layer/LiveAtlasLayerGroup";
import {LiveAtlasMarker, LiveAtlasMarkerSet} from "@/index";
import {nonReactiveState} from "@/store/state";
export default defineComponent({
props: {
@ -50,7 +51,7 @@ export default defineComponent({
createMarkers = () => {
const converter = currentMap.value!.locationToLatLng.bind(store.state.currentMap);
props.set.markers.forEach((marker: LiveAtlasMarker, id: string) => {
nonReactiveState.markers.get(props.set.id)!.markers.forEach((marker: LiveAtlasMarker, id: string) => {
const layer = createMarker(marker, converter);
layers.set(id, layer);
@ -102,7 +103,7 @@ export default defineComponent({
if(newValue && (!oldValue || oldValue.world === newValue.world)) {
const converter = currentMap.value!.locationToLatLng.bind(store.state.currentMap);
for (const [id, marker] of props.set.markers) {
for (const [id, marker] of nonReactiveState.markers.get(props.set.id)!.markers) {
updateMarker(layers.get(id), marker, converter);
}
}

5
src/index.d.ts vendored
View File

@ -228,7 +228,10 @@ interface LiveAtlasMarkerSet {
minZoom?: number;
maxZoom?: number;
showLabels?: boolean;
markers: Map<string, LiveAtlasMarker>;
}
interface LiveAtlasMarkerSetContents {
markers: Map<string, LiveAtlasMarker>,
areas: Map<string, LiveAtlasArea>;
lines: Map<string, LiveAtlasLine>;
circles: Map<string, LiveAtlasCircle>;

View File

@ -16,7 +16,7 @@
import {
HeadQueueEntry,
LiveAtlasMarkerSet,
LiveAtlasMarkerSet, LiveAtlasMarkerSetContents,
LiveAtlasPlayer,
LiveAtlasServerDefinition,
LiveAtlasWorldDefinition
@ -47,11 +47,14 @@ export default class DynmapMapProvider extends MapProvider {
private updateTimestamp: Date = new Date();
private updateInterval: number = 3000;
private markerSets: Map<string, LiveAtlasMarkerSet> = new Map();
private markers = new Map<string, LiveAtlasMarkerSetContents>();
constructor(config: LiveAtlasServerDefinition) {
super(config);
}
private async getMarkerSets(world: LiveAtlasWorldDefinition): Promise<Map<string, LiveAtlasMarkerSet>> {
private async getMarkerSets(world: LiveAtlasWorldDefinition): Promise<void> {
const url = `${this.config.dynmap!.markers}_markers_/marker_${world.name}.json`;
if(this.markersAbort) {
@ -61,7 +64,6 @@ export default class DynmapMapProvider extends MapProvider {
this.markersAbort = new AbortController();
const response = await this.getJSON(url, this.markersAbort.signal);
const sets: Map<string, LiveAtlasMarkerSet> = new Map();
response.sets = response.sets || {};
@ -71,21 +73,16 @@ export default class DynmapMapProvider extends MapProvider {
}
const set: MarkerSet = response.sets[key],
markers = buildMarkers(set.markers || {}),
circles = buildCircles(set.circles || {}),
areas = buildAreas(set.areas || {}),
lines = buildLines(set.lines || {});
markerSet = buildMarkerSet(key, set);
sets.set(key, {
...buildMarkerSet(key, set),
markers,
circles,
areas,
lines,
});
this.markerSets.set(key, markerSet);
this.markers.set(key, Object.seal({
markers: buildMarkers(set.markers || {}),
areas: buildAreas(set.areas || {}),
lines: buildLines(set.lines || {}),
circles: buildCircles(set.circles || {}),
}));
}
return sets;
}
async loadServerConfiguration(): Promise<void> {
@ -115,9 +112,13 @@ export default class DynmapMapProvider extends MapProvider {
}
async populateWorld(world: LiveAtlasWorldDefinition): Promise<void> {
const markerSets = await this.getMarkerSets(world);
await this.getMarkerSets(world);
this.store.commit(MutationTypes.SET_MARKER_SETS, markerSets);
this.store.commit(MutationTypes.SET_MARKER_SETS, this.markerSets);
this.store.commit(MutationTypes.SET_MARKERS, this.markers);
this.markerSets.clear();
this.markers.clear();
}
private async getUpdate(): Promise<void> {

View File

@ -22,7 +22,7 @@ import {
LiveAtlasDimension,
LiveAtlasLine,
LiveAtlasMarker,
LiveAtlasMarkerSet,
LiveAtlasMarkerSet, LiveAtlasMarkerSetContents,
LiveAtlasPartialComponentConfig,
LiveAtlasPlayer,
LiveAtlasServerConfig,
@ -56,6 +56,9 @@ export default class Pl3xmapMapProvider extends MapProvider {
components: LiveAtlasPartialComponentConfig,
}> = new Map();
private markerSets: Map<string, LiveAtlasMarkerSet> = new Map();
private markers = new Map<string, LiveAtlasMarkerSetContents>();
constructor(config: LiveAtlasServerDefinition) {
super(config);
}
@ -220,7 +223,7 @@ export default class Pl3xmapMapProvider extends MapProvider {
return components;
}
private async getMarkerSets(world: LiveAtlasWorldDefinition): Promise<Map<string, LiveAtlasMarkerSet>> {
private async getMarkerSets(world: LiveAtlasWorldDefinition): Promise<void> {
const url = `${this.config.pl3xmap}tiles/${world.name}/markers.json`;
if(this.markersAbort) {
@ -230,10 +233,9 @@ export default class Pl3xmapMapProvider extends MapProvider {
this.markersAbort = new AbortController();
const response = await Pl3xmapMapProvider.getJSON(url, this.markersAbort.signal);
const sets: Map<string, LiveAtlasMarkerSet> = new Map();
if(!Array.isArray(response)) {
return sets;
return;
}
response.forEach(set => {
@ -277,27 +279,19 @@ export default class Pl3xmapMapProvider extends MapProvider {
}
});
const e = {
this.markerSets.set(id, {
id,
label: set.name || "Unnamed set",
hidden: set.hide || false,
priority: set.order || 0,
showLabels: false,
markers,
circles,
areas,
lines,
};
sets.set(id, e);
showLabels: false
});
this.markers.set(id, Object.seal({markers, circles, areas, lines}));
});
return sets;
}
private static buildMarker(marker: any): LiveAtlasMarker {
return Object.seal({
return {
location: {
x: marker.point?.x || 0,
y: 0,
@ -308,11 +302,11 @@ export default class Pl3xmapMapProvider extends MapProvider {
label: (marker.tooltip || '').trim(),
isLabelHTML: true
});
};
}
private static buildRectangle(area: any): LiveAtlasArea {
return Object.seal({
return {
style: {
stroke: typeof area.stroke !== 'undefined' ? !!area.stroke : true,
color: area.color || '#3388ff',
@ -334,11 +328,11 @@ export default class Pl3xmapMapProvider extends MapProvider {
tooltipContent: area.tooltip,
popupContent: area.popup,
isPopupHTML: true,
});
};
}
private static buildArea(area: any): LiveAtlasArea {
return Object.seal({
return {
style: {
stroke: typeof area.stroke !== 'undefined' ? !!area.stroke : true,
color: area.color || '#3388ff',
@ -355,11 +349,11 @@ export default class Pl3xmapMapProvider extends MapProvider {
tooltipContent: area.tooltip,
popupContent: area.popup,
isPopupHTML: true,
});
};
}
private static buildLine(line: any): LiveAtlasLine {
return Object.seal({
return {
style: {
stroke: typeof line.stroke !== 'undefined' ? !!line.stroke : true,
color: line.color || '#3388ff',
@ -371,11 +365,11 @@ export default class Pl3xmapMapProvider extends MapProvider {
tooltipContent: line.tooltip,
popupContent: line.popup,
isPopupHTML: true,
});
};
}
private static buildCircle(circle: any): LiveAtlasCircle {
return Object.seal({
return {
location: {
x: circle.center?.x || 0,
y: 0,
@ -396,7 +390,7 @@ export default class Pl3xmapMapProvider extends MapProvider {
tooltipContent: circle.tooltip,
popupContent: circle.popup,
isPopupHTML: true
});
};
}
async loadServerConfiguration(): Promise<void> {
@ -427,14 +421,18 @@ export default class Pl3xmapMapProvider extends MapProvider {
}
async populateWorld(world: LiveAtlasWorldDefinition) {
const markerSets = await this.getMarkerSets(world),
worldConfig = this.worldComponents.get(world.name);
const worldConfig = this.worldComponents.get(world.name);
await this.getMarkerSets(world);
this.playerUpdateInterval = this.worldPlayerUpdateIntervals.get(world.name) || 3000;
this.markerUpdateInterval = this.worldMarkerUpdateIntervals.get(world.name) || 3000;
this.store.commit(MutationTypes.SET_MARKER_SETS, markerSets);
this.store.commit(MutationTypes.SET_MARKER_SETS, this.markerSets);
this.store.commit(MutationTypes.SET_MARKERS, this.markers);
this.store.commit(MutationTypes.SET_COMPONENTS, worldConfig!.components);
this.markerSets.clear();
this.markers.clear();
}
private async getPlayers(): Promise<Set<LiveAtlasPlayer>> {
@ -453,7 +451,7 @@ export default class Pl3xmapMapProvider extends MapProvider {
players.add({
name: (player.name || '').toLowerCase(),
uuid: player.uuid,
displayName: player.name || "",
displayName: player.display_name || player.name || "",
health: player.health || 0,
armor: player.armor || 0,
sort: 0,

View File

@ -23,6 +23,7 @@ export enum MutationTypes {
SET_WORLDS = 'setWorlds',
SET_COMPONENTS = 'setComponents',
SET_MARKER_SETS = 'setMarkerSets',
SET_MARKERS = 'setMarkers',
SET_WORLD_STATE = 'setWorldState',
ADD_MARKER_SET_UPDATES = 'addMarkerSetUpdates',
ADD_TILE_UPDATES = 'addTileUpdates',

View File

@ -16,7 +16,7 @@
import {MutationTree} from "vuex";
import {MutationTypes} from "@/store/mutation-types";
import {State} from "@/store/state";
import {nonReactiveState, State} from "@/store/state";
import {
DynmapMarkerSetUpdates,
DynmapTileUpdate
@ -44,7 +44,7 @@ import {
LiveAtlasPartialComponentConfig,
LiveAtlasComponentConfig,
LiveAtlasUIModal,
LiveAtlasSidebarSectionState
LiveAtlasSidebarSectionState, LiveAtlasMarkerSetContents
} from "@/index";
import DynmapMapProvider from "@/providers/DynmapMapProvider";
import Pl3xmapMapProvider from "@/providers/Pl3xmapMapProvider";
@ -61,7 +61,8 @@ export type Mutations<S = State> = {
[MutationTypes.SET_SERVER_MESSAGES](state: S, messages: LiveAtlasServerMessageConfig): void
[MutationTypes.SET_WORLDS](state: S, worlds: Array<LiveAtlasWorldDefinition>): void
[MutationTypes.SET_COMPONENTS](state: S, components: LiveAtlasPartialComponentConfig | LiveAtlasComponentConfig): void
[MutationTypes.SET_MARKER_SETS](state: S, worlds: Map<string, LiveAtlasMarkerSet>): void
[MutationTypes.SET_MARKER_SETS](state: S, markerSets: Map<string, LiveAtlasMarkerSet>): void
[MutationTypes.SET_MARKERS](state: S, markers: Map<string, LiveAtlasMarkerSetContents>): void
[MutationTypes.SET_WORLD_STATE](state: S, worldState: LiveAtlasWorldState): void
[MutationTypes.ADD_MARKER_SET_UPDATES](state: S, updates: Map<string, DynmapMarkerSetUpdates>): void
[MutationTypes.ADD_TILE_UPDATES](state: S, updates: Array<DynmapTileUpdate>): void
@ -259,6 +260,7 @@ export const mutations: MutationTree<State> & Mutations = {
[MutationTypes.SET_MARKER_SETS](state: State, markerSets: Map<string, LiveAtlasMarkerSet>) {
state.markerSets.clear();
state.pendingSetUpdates.clear();
nonReactiveState.markers.clear();
for(const entry of markerSets) {
state.markerSets.set(entry[0], entry[1]);
@ -268,6 +270,21 @@ export const mutations: MutationTree<State> & Mutations = {
circleUpdates: [],
lineUpdates: [],
});
nonReactiveState.markers.set(entry[0], {
markers: new Map<string, LiveAtlasMarker>(),
areas: new Map<string, LiveAtlasArea>(),
lines: new Map<string, LiveAtlasLine>(),
circles: new Map<string, LiveAtlasCircle>(),
});
}
},
//Sets the existing marker sets from the last marker fetch
[MutationTypes.SET_MARKERS](state: State, markers: Map<string, LiveAtlasMarkerSetContents>) {
nonReactiveState.markers.clear();
for(const entry of markers) {
nonReactiveState.markers.set(entry[0], entry[1]);
}
},
@ -291,10 +308,6 @@ export const mutations: MutationTree<State> & Mutations = {
priority: entry[1].payload.priority,
label: entry[1].payload.label,
hidden: entry[1].payload.hidden,
markers: Object.freeze(new Map()) as Map<string, LiveAtlasMarker>,
areas: Object.freeze(new Map()) as Map<string, LiveAtlasArea>,
circles: Object.freeze(new Map()) as Map<string, LiveAtlasCircle>,
lines: Object.freeze(new Map()) as Map<string, LiveAtlasLine>,
});
state.pendingSetUpdates.set(entry[0], {
@ -310,6 +323,7 @@ export const mutations: MutationTree<State> & Mutations = {
}
const set = state.markerSets.get(entry[0]) as LiveAtlasMarkerSet,
setContents = nonReactiveState.markers.get(entry[0]) as LiveAtlasMarkerSetContents,
setUpdates = state.pendingSetUpdates.get(entry[0]) as DynmapMarkerSetUpdates;
//Delete the set if it has been deleted
@ -332,33 +346,33 @@ export const mutations: MutationTree<State> & Mutations = {
//Update non-reactive lists
for(const update of entry[1].markerUpdates) {
if(update.removed) {
set.markers.delete(update.id);
setContents.markers.delete(update.id);
} else {
set.markers.set(update.id, update.payload as LiveAtlasMarker);
setContents.markers.set(update.id, update.payload as LiveAtlasMarker);
}
}
for(const update of entry[1].areaUpdates) {
if(update.removed) {
set.areas.delete(update.id);
setContents.areas.delete(update.id);
} else {
set.areas.set(update.id, update.payload as LiveAtlasArea);
setContents.areas.set(update.id, update.payload as LiveAtlasArea);
}
}
for(const update of entry[1].circleUpdates) {
if(update.removed) {
set.circles.delete(update.id);
setContents.circles.delete(update.id);
} else {
set.circles.set(update.id, update.payload as LiveAtlasCircle);
setContents.circles.set(update.id, update.payload as LiveAtlasCircle);
}
}
for(const update of entry[1].lineUpdates) {
if(update.removed) {
set.lines.delete(update.id);
setContents.lines.delete(update.id);
} else {
set.lines.set(update.id, update.payload as LiveAtlasLine);
setContents.lines.set(update.id, update.payload as LiveAtlasLine);
}
}

View File

@ -35,7 +35,8 @@ import {
LiveAtlasServerConfig,
LiveAtlasChat,
LiveAtlasUIModal,
LiveAtlasSidebarSectionState
LiveAtlasSidebarSectionState,
LiveAtlasMarkerSetContents
} from "@/index";
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
@ -189,7 +190,8 @@ export const state: State = {
messages: [],
},
markerSets: new Map(), //Markers from world_markers.json. Contents of each set isn't reactive for performance reasons.
markerSets: new Map(), //Markers sets from world_markers.json, doesn't include the markers themselves for performance reasons
pendingSetUpdates: new Map(), //Pending updates to markers/areas/etc for each marker set
pendingTileUpdates: [], //Pending updates to map tiles
@ -274,3 +276,7 @@ export const state: State = {
},
}
};
export const nonReactiveState = Object.freeze({
markers: new Map<string, LiveAtlasMarkerSetContents>(),
});

View File

@ -285,14 +285,14 @@ export function buildMarker(data: Marker): LiveAtlasMarker {
let dimensions;
if(data.dim) {
dimensions = data.dim.split('x').slice(0, 2).map(value => parseInt(value));
dimensions = data.dim.split('x').filter(value => !isNaN(Number(value)));
if(dimensions.length !== 2) {
dimensions = undefined;
}
}
return Object.seal({
return {
label: data.label || '',
//Dynmap#2288 currently means markup:false markers are still encoded
//The planned solution for this is to always treat everything as HTML, so we'll do that here
@ -307,7 +307,7 @@ export function buildMarker(data: Marker): LiveAtlasMarker {
minZoom: typeof data.minzoom !== 'undefined' && data.minzoom > -1 ? data.minzoom : undefined,
maxZoom: typeof data.maxzoom !== 'undefined' && data.maxzoom > -1 ? data.maxzoom : undefined,
popupContent: data.desc || undefined,
});
};
}
export function buildAreas(data: any): Map<string, LiveAtlasArea> {
@ -325,12 +325,7 @@ export function buildAreas(data: any): Map<string, LiveAtlasArea> {
}
export function buildArea(area: MarkerArea): LiveAtlasArea {
const opacity = area.fillopacity || 0,
x = area.x || [0, 0],
y: [number, number] = [area.ybottom || 0, area.ytop || 0],
z = area.z || [0, 0];
return Object.seal({
return {
style: {
color: area.color || '#ff0000',
opacity: area.opacity || 1,
@ -338,8 +333,12 @@ export function buildArea(area: MarkerArea): LiveAtlasArea {
fillColor: area.fillcolor || '#ff0000',
fillOpacity: area.fillopacity || 0,
},
outline: !opacity,
points: getPoints(x, y, z, !opacity),
outline: !area.fillopacity,
points: getPoints(
area.x || [0, 0],
[area.ybottom || 0, area.ytop || 0],
area.z || [0, 0],
!area.fillopacity),
minZoom: typeof area.minzoom !== 'undefined' && area.minzoom > -1 ? area.minzoom : undefined,
maxZoom: typeof area.maxzoom !== 'undefined' && area.maxzoom > -1 ? area.maxzoom : undefined,
@ -347,7 +346,7 @@ export function buildArea(area: MarkerArea): LiveAtlasArea {
//The planned solution for this is to always treat everything as HTML, so we'll do that here
isPopupHTML: true, //area.desc ? true : area.markup || false,
popupContent: area.desc || area.label || undefined,
});
};
}
export function buildLines(data: any): Map<string, LiveAtlasLine> {
@ -365,7 +364,7 @@ export function buildLines(data: any): Map<string, LiveAtlasLine> {
}
export function buildLine(line: MarkerLine): LiveAtlasLine {
return Object.seal({
return {
style: {
color: line.color || '#ff0000',
opacity: line.opacity || 1,
@ -379,7 +378,7 @@ export function buildLine(line: MarkerLine): LiveAtlasLine {
//The planned solution for this is to always treat everything as HTML, so we'll do that here
isPopupHTML: true,
popupContent: line.desc || line.label || undefined,
});
};
}
export function buildCircles(data: any): Map<string, LiveAtlasCircle> {
@ -397,7 +396,7 @@ export function buildCircles(data: any): Map<string, LiveAtlasCircle> {
}
export function buildCircle(circle: MarkerCircle): LiveAtlasCircle {
return Object.seal({
return {
location: {
x: circle.x || 0,
y: circle.y || 0,
@ -418,7 +417,7 @@ export function buildCircle(circle: MarkerCircle): LiveAtlasCircle {
//The planned solution for this is to always treat everything as HTML, so we'll do that here
isPopupHTML: true,
popupContent: circle.desc || circle.label || undefined,
});
};
}
export function buildUpdates(data: Array<any>, lastUpdate: Date) {