From a65cf330596743a13c970ec7c1f5076f6c430892 Mon Sep 17 00:00:00 2001 From: Buhallin Date: Wed, 11 Jan 2023 00:57:06 -0800 Subject: [PATCH 1/3] Optimize location connection drawing during drag operations Previously the entire location map was being rebuilt and redrawn in onUpdate() during a drag operation, including the connectors which weren't changing. This was causing notable lag on some systems. This splits the draw operations into two separate pieces - locations on the board which are unchanging, and locations which are being dragged. This allows only connections which are actually moving to be recalculated. Since TTS only allows a single list of vector lines per object the dragged lines are now set on the cards themselves rather than the PlayArea. This may possibly improve performance in itself, by keeping each list of vectors smaller. Supporting this required some changes in how pre-built connections are handled. --- src/core/PlayArea.ttslua | 117 +++++++++++++++++++++++++++++---------- 1 file changed, 88 insertions(+), 29 deletions(-) diff --git a/src/core/PlayArea.ttslua b/src/core/PlayArea.ttslua index 21f07b8b..200b1fa0 100644 --- a/src/core/PlayArea.ttslua +++ b/src/core/PlayArea.ttslua @@ -3,14 +3,17 @@ --------------------------------------------------------- -- set true to enable debug logging -local DEBUG = false +local DEBUG = true -- Location connection directional options local BIDIRECTIONAL = 0 local ONE_WAY = 1 +local INCOMING_ONE_WAY = 2 -- Connector draw parameters local CONNECTION_THICKNESS = 0.015 +local DRAGGING_CONNECTION_THICKNESS = 0.15 +local DRAGGING_CONNECTION_COLOR = { 0.8, 0.8, 0.8, 1 } local CONNECTION_COLOR = { 0.4, 0.4, 0.4, 1 } local DIRECTIONAL_ARROW_DISTANCE = 3.5 local ARROW_ARM_LENGTH = 0.9 @@ -91,7 +94,7 @@ end function onCollisionEnter(collisionInfo) local obj = collisionInfo.collision_object local objType = obj.name - + -- only continue for cards if not collisionEnabled or (objType ~= "Card" and objType ~= "CardCustom") then return end @@ -151,6 +154,11 @@ function onUpdate() if obj == nil or not isInPlayArea(obj) then draggingGuids[guid] = nil needsConnectionRebuild = true + -- If object still exists then it's been dragged outside the area and needs to clear the + -- lines attached to it + if obj ~= nil then + obj.setVectorLines(nil) + end end -- Even if the last location left the play area, need one last draw to clear the lines needsConnectionDraw = true @@ -159,7 +167,7 @@ function onUpdate() rebuildConnectionList() end if needsConnectionDraw then - drawConnections() + drawDraggingConnections() end end @@ -174,7 +182,7 @@ function maybeTrackLocation(card) if metadata.type == "Location" then locations[card.getGUID()] = metadata rebuildConnectionList() - drawConnections() + drawBaseConnections() end end end @@ -190,14 +198,14 @@ function maybeUntrackLocation(card) if locations[card.getGUID()] ~= nil and not card.locked then locations[card.getGUID()] = nil rebuildConnectionList() - drawConnections() + drawBaseConnections() end end -- Builds a list of GUID to GUID connection information based on the currently tracked locations. -- This will update the connection information and store it in the locationConnections data member, -- but does not draw those connections. This should often be followed by a call to --- drawConnections() +-- drawBaseConnections() function rebuildConnectionList() if not showLocationLinks() then locationConnections = { } @@ -256,10 +264,15 @@ function buildConnection(cardId, iconCardList) for _, connectedGuid in ipairs(iconCardList[icon]) do -- If the reciprocal exists, convert it to BiDi, otherwise add as a one-way if locationConnections[connectedGuid] ~= nil - and locationConnections[connectedGuid][card.getGUID()] ~= nil then + and locationConnections[connectedGuid][card.getGUID()] == ONE_WAY then locationConnections[connectedGuid][card.getGUID()] = BIDIRECTIONAL + locationConnections[card.getGUID()][connectedGuid] = nil else + if locationConnections[connectedGuid] == nil then + locationConnections[connectedGuid] = { } + end locationConnections[card.getGUID()][connectedGuid] = ONE_WAY + locationConnections[connectedGuid][card.getGUID()] = INCOMING_ONE_WAY end end end @@ -283,8 +296,9 @@ function getLocationData(card) end end --- Draws the lines for connections currently in locationConnections. -function drawConnections() +-- Draws the lines for connections currently in locationConnections but not in draggingGuids. +-- Constructed vectors will be set to the playmat +function drawBaseConnections() if not showLocationLinks() then locationConnections = { } return @@ -295,14 +309,16 @@ function drawConnections() -- Objects should reliably exist at this point, but since this can be called during onUpdate the -- object checks are conservative just to make sure. local origin = getObjectFromGUID(originGuid) - if origin != nil then + if draggingGuids[originGuid] == nil and origin != nil then for targetGuid, direction in pairs(targetGuids) do local target = getObjectFromGUID(targetGuid) - if target != nil then + if draggingGuids[targetGuid] == nil and target != nil then + -- Since we process the full list, we're guaranteed to hit any ONE_WAY connections later + -- so we can ignore INCOMING_ONE_WAY if direction == BIDIRECTIONAL then - addBidirectionalVector(origin, target, cardConnectionLines) + addBidirectionalVector(origin, target, self, cardConnectionLines) elseif direction == ONE_WAY then - addOneWayVector(origin, target, cardConnectionLines) + addOneWayVector(origin, target, self, cardConnectionLines) end end end @@ -311,22 +327,61 @@ function drawConnections() self.setVectorLines(cardConnectionLines) end +-- Draws the lines for cards which are currently being dragged. +function drawDraggingConnections() + if not showLocationLinks() then + return + end + local cardConnectionLines = { } + local ownedVectors = { } + + for originGuid, _ in pairs(draggingGuids) do + targetGuids = locationConnections[originGuid] + -- Objects should reliably exist at this point, but since this can be called during onUpdate the + -- object checks are conservative just to make sure. + local origin = getObjectFromGUID(originGuid) + if draggingGuids[originGuid] and origin != nil then + ownedVectors[originGuid] = { } + for targetGuid, direction in pairs(targetGuids) do + local target = getObjectFromGUID(targetGuid) + if target != nil then + if direction == BIDIRECTIONAL then + addBidirectionalVector(origin, target, origin, ownedVectors[originGuid]) + elseif direction == ONE_WAY then + addOneWayVector(origin, target, origin, ownedVectors[originGuid]) + elseif direction == INCOMING_ONE_WAY and not draggingGuids[targetGuid] then + addOneWayVector(target, origin, origin, ownedVectors[originGuid]) + end + end + end + end + end + for ownerGuid, vectors in pairs(ownedVectors) do + local card = getObjectFromGUID(ownerGuid) + card.setVectorLines(vectors) + end +end + -- Draws a bidirectional location connection between the two cards, adding the lines to do so to the -- given lines list. ---@param card1 Object One of the card objects to connect ---@param card2 Object The other card object to connect +---@param vectorOwner Object The object which these lines will be set to. Used for relative +--- positioning and scaling, as well as highlighting connections during a drag operation ---@param lines Table List of vector line elements. Mutable, will be updated to add this connector -function addBidirectionalVector(card1, card2, lines) +function addBidirectionalVector(card1, card2, vectorOwner, lines) local cardPos1 = card1.getPosition() local cardPos2 = card2.getPosition() cardPos1.y = CONNECTION_LINE_Y cardPos2.y = CONNECTION_LINE_Y - local pos1 = self.positionToLocal(cardPos1) - local pos2 = self.positionToLocal(cardPos2) + + local pos1 = vectorOwner.positionToLocal(cardPos1) + local pos2 = vectorOwner.positionToLocal(cardPos2) + table.insert(lines, { points = { pos1, pos2 }, - color = CONNECTION_COLOR, - thickness = CONNECTION_THICKNESS, + color = vectorOwner == self and CONNECTION_COLOR or DRAGGING_CONNECTION_COLOR, + thickness = vectorOwner == self and CONNECTION_THICKNESS or DRAGGING_CONNECTION_THICKNESS, }) end @@ -334,10 +389,12 @@ end -- given lines list. Arrows will point towards the target card. ---@param origin Object Origin card in the connection ---@param target Object Target card object to connect +---@param vectorOwner Object The object which these lines will be set to. Used for relative +--- positioning and scaling, as well as highlighting connections during a drag operation ---@param lines Table List of vector line elements. Mutable, will be updated to add this connector -function addOneWayVector(origin, target, lines) +function addOneWayVector(origin, target, vectorOwner, lines) -- Start with the BiDi then add the arrow lines to it - addBidirectionalVector(origin, target, lines) + addBidirectionalVector(origin, target, vectorOwner, lines) local originPos = origin.getPosition() local targetPos = target.getPosition() originPos.y = CONNECTION_LINE_Y @@ -355,28 +412,30 @@ function addOneWayVector(origin, target, lines) local closeToTarget = Vector(targetPos):moveTowards(originPos, distanceFromCard - ARROW_ARM_LENGTH / 2) if (originPos:distance(closeToOrigin) > originPos:distance(closeToTarget)) then - addArrowLines(midpoint, originPos, lines) + addArrowLines(midpoint, originPos, vectorOwner, lines) else - addArrowLines(closeToOrigin, originPos, lines) - addArrowLines(closeToTarget, originPos, lines) + addArrowLines(closeToOrigin, originPos, vectorOwner, lines) + addArrowLines(closeToTarget, originPos, vectorOwner, lines) end end -- Draws an arrowhead at the given position. ---@param arrowheadPosition Table Centerpoint of the arrowhead to draw (NOT the tip of the arrow) ---@param originPos Table Origin point of the connection, used to position the arrow arms +---@param vectorOwner Object The object which these lines will be set to. Used for relative +--- positioning and scaling, as well as highlighting connections during a drag operation ---@param lines Table List of vector line elements. Mutable, will be updated to add this arrow -function addArrowLines(arrowheadPos, originPos, lines) +function addArrowLines(arrowheadPos, originPos, vectorOwner, lines) local arrowArm1 = Vector(arrowheadPos):moveTowards(originPos, ARROW_ARM_LENGTH):sub(arrowheadPos):rotateOver("y", -1 * ARROW_ANGLE):add(arrowheadPos) local arrowArm2 = Vector(arrowheadPos):moveTowards(originPos, ARROW_ARM_LENGTH):sub(arrowheadPos):rotateOver("y", ARROW_ANGLE):add(arrowheadPos) - local head = self.positionToLocal(arrowheadPos) - local arm1 = self.positionToLocal(arrowArm1) - local arm2 = self.positionToLocal(arrowArm2) + local head = vectorOwner.positionToLocal(arrowheadPos) + local arm1 = vectorOwner.positionToLocal(arrowArm1) + local arm2 = vectorOwner.positionToLocal(arrowArm2) table.insert(lines, { points = { arm1, head, arm2}, - color = CONNECTION_COLOR, - thickness = CONNECTION_THICKNESS + color = vectorOwner == self and CONNECTION_COLOR or DRAGGING_CONNECTION_COLOR, + thickness = vectorOwner == self and CONNECTION_THICKNESS or DRAGGING_CONNECTION_THICKNESS, }) end From 6b52e412e9f74d2d63ec69f90a70e8dc6f55e1d6 Mon Sep 17 00:00:00 2001 From: Buhallin Date: Wed, 11 Jan 2023 03:36:11 -0800 Subject: [PATCH 2/3] Turn off debug --- src/core/PlayArea.ttslua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/PlayArea.ttslua b/src/core/PlayArea.ttslua index 200b1fa0..e0f7cd94 100644 --- a/src/core/PlayArea.ttslua +++ b/src/core/PlayArea.ttslua @@ -3,7 +3,7 @@ --------------------------------------------------------- -- set true to enable debug logging -local DEBUG = true +local DEBUG = false -- Location connection directional options local BIDIRECTIONAL = 0 From e6854801fb838df522ff4bc2943be44c61412c4a Mon Sep 17 00:00:00 2001 From: Buhallin Date: Wed, 11 Jan 2023 13:42:58 -0800 Subject: [PATCH 3/3] Clear vector lines from cards when they're not being dragged The events which can indicate a drag stop are varied: - Drag a card into another on the table (tryObjectEnterContainer) - Drag multiple cards into another on the table (onCollisionEnter, tryObjectEnterContainer) - Group multiple cards while holding them (tryObjectEnterContainer) --- src/core/Global.ttslua | 10 +++++++++- src/core/PlayArea.ttslua | 19 ++++++++++++++++++- src/core/PlayAreaApi.ttslua | 9 ++++++++- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/core/Global.ttslua b/src/core/Global.ttslua index 6ede2b21..abac3231 100644 --- a/src/core/Global.ttslua +++ b/src/core/Global.ttslua @@ -184,6 +184,14 @@ function onObjectSearchEnd(object, playerColor) end end +-- Pass object enter container events to the PlayArea to clear vector lines from dragged cards. +-- This requires the try method as cards won't exist any more after they enter a deck, so the lines +-- can't be cleared. +function tryObjectEnterContainer(container, object) + playAreaAPI.tryObjectEnterContainer(container, object) + return true +end + function drawEncountercard(params) local position = params[1] local rotation = params[2] @@ -875,7 +883,7 @@ function applyOptionPanelChange(id, state) -- option: Show CYOA campaign guides elseif id == "showCYOA" then optionPanel[id] = spawnOrRemoveHelper(state, "CYOA Campaign Guides", {65, 1.6, -11}) - + -- option: Show custom playmat images elseif id == "showCustomPlaymatImages" then optionPanel[id] = spawnOrRemoveHelper(state, "Custom Playmat Images", {67.5, 1.6, 37}) diff --git a/src/core/PlayArea.ttslua b/src/core/PlayArea.ttslua index e0f7cd94..10de9821 100644 --- a/src/core/PlayArea.ttslua +++ b/src/core/PlayArea.ttslua @@ -103,7 +103,12 @@ function onCollisionEnter(collisionInfo) if shouldSpawnTokens(card) then tokenManager.spawnForCard(card) end - draggingGuids[card.getGUID()] = nil + -- If this card was being dragged, clear the dragging connections. A multi-drag/drop may send + -- the dropped card immediately into a deck, so this has to be done here + if draggingGuids[card.getGUID()] ~= nil then + card.setVectorLines(nil) + draggingGuids[card.getGUID()] = nil + end maybeTrackLocation(card) end @@ -202,6 +207,18 @@ function maybeUntrackLocation(card) end end +-- Global event handler, delegated from Global. Clears any connection lines from dragged cards +-- before they are destroyed by entering a deck. Removal of the card from the dragging list will +-- be handled during the next onUpdate() call. +function tryObjectEnterContainer(params) + for draggedGuid, _ in pairs(draggingGuids) do + local draggedObj = getObjectFromGUID(draggedGuid) + if draggedObj ~= nil then + draggedObj.setVectorLines(nil) + end + end +end + -- Builds a list of GUID to GUID connection information based on the currently tracked locations. -- This will update the connection information and store it in the locationConnections data member, -- but does not draw those connections. This should often be followed by a call to diff --git a/src/core/PlayAreaApi.ttslua b/src/core/PlayAreaApi.ttslua index 157cc600..65ac8487 100644 --- a/src/core/PlayAreaApi.ttslua +++ b/src/core/PlayAreaApi.ttslua @@ -47,6 +47,13 @@ do PlayAreaApi.setLimitSnapsByType = function(matchCardTypes) getObjectFromGUID(PLAY_AREA_GUID).call("setLimitSnapsByType", matchCardTypes) end - + + -- Receiver for the Global tryObjectEnterContainer event. Used to clear vector lines from dragged + -- cards before they're destroyed by entering the container + PlayAreaApi.tryObjectEnterContainer = function(container, object) + getObjectFromGUID(PLAY_AREA_GUID).call("tryObjectEnterContainer", + { container = container, object = object }) + end + return PlayAreaApi end