From 5d25f637119f1560ce8f8171cd46123c0bba56b4 Mon Sep 17 00:00:00 2001
From: Terry Truong <terry06890@gmail.com>
Date: Sat, 7 Jan 2023 19:48:36 +1100
Subject: Throttle relayouting of events

Reduces CPU drain, but adds noticeable gap between panning and
event movement.
---
 src/components/TimeLine.vue | 96 ++++++++++++++++++++++++++-------------------
 1 file changed, 55 insertions(+), 41 deletions(-)

(limited to 'src/components/TimeLine.vue')

diff --git a/src/components/TimeLine.vue b/src/components/TimeLine.vue
index 9e0a48c..2f58328 100644
--- a/src/components/TimeLine.vue
+++ b/src/components/TimeLine.vue
@@ -435,7 +435,14 @@ function dateToOffset(date: HistDate){ // Assumes 'date' is >=firstDate and <=la
 		return tick.offset + getUnitDiff(tick.date, date, minorScale.value) / getNumSubUnits(tick.date, scaleIdx.value);
 	}
 }
-const idToEvent = computed(() => { // Maps visible event IDs to HistEvents
+const idToEvent: Ref<Map<number, HistEvent>> = ref(new Map()); // Maps visible event IDs to HistEvents
+const idToPos: Ref<Map<number, [number, number, number, number]>> = ref(new Map()); // Maps event IDs to x/y/w/h
+const idsToSkipTransition: Ref<Set<number>> = ref(new Set()); // Used to prevent events moving across mainline
+type LineCoords = [number, number, number, number]; // x, y, length, angle
+const eventLines: Ref<Map<number, LineCoords>> = ref(new Map()); // Maps event ID to event line data
+const UPDATE_TIMEOUT = 100; // Used to throttle relayouting
+let layoutUpdateHdlr = 0;
+function updateIdToEvent(){
 	let map: Map<number, HistEvent> = new Map();
 	// Find events to display
 	let itr = props.eventTree.lowerBound(new HistEvent(0, '', firstDate.value));
@@ -450,17 +457,8 @@ const idToEvent = computed(() => { // Maps visible event IDs to HistEvents
 		}
 		map.set(event.id, event);
 	}
-	return map;
-});
-watch(idToEvent, () => { // Remove highlighting of search results that have become out of range
-	if (searchEvent.value != null && !idToEvent.value.has(searchEvent.value.id)){
-		searchEvent.value = null;
-	}
-});
-const idToPos: Ref<Map<number, [number, number, number, number]>> = ref(new Map()); // Maps event IDs to x/y/w/h
-const idsToSkipTransition: Ref<Set<number>> = ref(new Set()); // Used to prevent events moving across mainline
-type LineCoords = [number, number, number, number]; // x, y, length, angle
-const eventLines: Ref<Map<number, LineCoords>> = ref(new Map()); // Maps event ID to event line data
+	idToEvent.value = map;
+}
 function getEventLayout(): Map<number, [number, number, number, number]> {
 	let map: Map<number, [number, number, number, number]> = new Map();
 	if (!mounted.value){
@@ -633,36 +631,10 @@ function getEventLayout(): Map<number, [number, number, number, number]> {
 	}
 	return map;
 }
-watch(idToEvent, () => { // Updates idToPos and eventLines
-	let map = getEventLayout();
-	// Check for events that cross mainline
-	idsToSkipTransition.value.clear();
-	for (let [eventId, [x, y, , ]] of map.entries()){
-		if (idToPos.value.has(eventId)){
-			let [oldX, oldY, , ] = idToPos.value.get(eventId)!;
-			if (props.vert && (oldX - mainlineOffset.value) * (x - mainlineOffset.value) < 0
-					|| !props.vert && (oldY - mainlineOffset.value) * (y - mainlineOffset.value) < 0){
-				idsToSkipTransition.value.add(eventId);
-			}
-		}
-	}
-	// Update idToPos // Note: For some reason, if the map is assigned directly, events won't consistently transition
-	let toDelete = [];
-	for (let eventId of idToPos.value.keys()){
-		if (!map.has(eventId)){
-			toDelete.push(eventId);
-		}
-	}
-	for (let eventId of toDelete){
-		idToPos.value.delete(eventId);
-	}
-	for (let [eventId, pos] of map.entries()){
-		idToPos.value.set(eventId, pos);
-	}
-	// Update event lines
+function updateEventLines(){
 	let newEventLines: Map<number, LineCoords> = new Map();
 	let numUnits = getNumDisplayUnits();
-	for (let [id, [eventX, eventY, eventW, eventH]] of map){
+	for (let [id, [eventX, eventY, eventW, eventH]] of idToPos.value.entries()){
 		let x: number; // For line end on mainline
 		let y: number;
 		let x2: number; // For line end at event
@@ -700,9 +672,51 @@ watch(idToEvent, () => { // Updates idToPos and eventLines
 		newEventLines.set(id, [x, y, l, a]);
 	}
 	eventLines.value = newEventLines;
+}
+function updateLayoutData(){ // Updates idToEvent, idToPos, idsToSkipTransition, and eventLines
+	// Update idToEvent
+	updateIdToEvent()
+	// Remove highlighting of search results that have become out of range
+	if (searchEvent.value != null && !idToEvent.value.has(searchEvent.value.id)){
+		searchEvent.value = null;
+	}
+	// Get layout
+	let map = getEventLayout();
+	// Check for events that cross mainline
+	idsToSkipTransition.value.clear();
+	for (let [eventId, [x, y, , ]] of map.entries()){
+		if (idToPos.value.has(eventId)){
+			let [oldX, oldY, , ] = idToPos.value.get(eventId)!;
+			if (props.vert && (oldX - mainlineOffset.value) * (x - mainlineOffset.value) < 0
+					|| !props.vert && (oldY - mainlineOffset.value) * (y - mainlineOffset.value) < 0){
+				idsToSkipTransition.value.add(eventId);
+			}
+		}
+	}
+	// Update idToPos // Note: For some reason, if the map is assigned directly, events won't consistently transition
+	let toDelete = [];
+	for (let eventId of idToPos.value.keys()){
+		if (!map.has(eventId)){
+			toDelete.push(eventId);
+		}
+	}
+	for (let eventId of toDelete){
+		idToPos.value.delete(eventId);
+	}
+	for (let [eventId, pos] of map.entries()){
+		idToPos.value.set(eventId, pos);
+	}
+	// Update event lines
+	updateEventLines();
 	// Notify parent
 	emit('event-display', ID, [...map.keys()], firstDate.value, lastDate.value, minorScaleIdx.value);
-});
+}
+function updateLayoutDataThrottled(){
+	clearTimeout(layoutUpdateHdlr);
+	layoutUpdateHdlr = window.setTimeout(updateLayoutData, UPDATE_TIMEOUT);
+}
+watch(firstDate, updateLayoutDataThrottled);
+watch(() => props.eventTree, updateLayoutDataThrottled);
 
 // For event-count indicators
 const tickToCount = computed((): Map<number, number> => {
-- 
cgit v1.2.3

