mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-26 10:34:07 +00:00
Enhance logging with detailed timing information for API and session operations
- Added timing logs to various functions in `ApiContext`, `ServeSession`, and `Reconciler` to track performance and improve debugging. - Introduced detailed trace logs for connection, reading, writing, and patch application processes, providing insights into execution times. - Updated the `App` component to log the patch tree building process, enhancing visibility during session startup. These changes aim to improve performance monitoring and facilitate troubleshooting within the application.
This commit is contained in:
parent
a0e07bb542
commit
7b9d44953b
6 changed files with 223 additions and 15 deletions
|
|
@ -164,9 +164,22 @@ end
|
||||||
function ApiContext:connect()
|
function ApiContext:connect()
|
||||||
local url = ("%s/api/rojo"):format(self.__baseUrl)
|
local url = ("%s/api/rojo"):format(self.__baseUrl)
|
||||||
|
|
||||||
|
local connectClock = os.clock()
|
||||||
|
Log.trace("[TIMING] ApiContext:connect() GET {} starting", url)
|
||||||
return Http.get(url)
|
return Http.get(url)
|
||||||
|
:andThen(function(response)
|
||||||
|
Log.trace(
|
||||||
|
"[TIMING] ApiContext:connect() HTTP response received ({:.1} ms)",
|
||||||
|
(os.clock() - connectClock) * 1000
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
end)
|
||||||
:andThen(rejectFailedRequests)
|
:andThen(rejectFailedRequests)
|
||||||
:andThen(Http.Response.msgpack)
|
:andThen(Http.Response.msgpack)
|
||||||
|
:andThen(function(body)
|
||||||
|
Log.trace("[TIMING] ApiContext:connect() msgpack decoded")
|
||||||
|
return body
|
||||||
|
end)
|
||||||
:andThen(rejectWrongVersion)
|
:andThen(rejectWrongVersion)
|
||||||
:andThen(rejectWrongFork)
|
:andThen(rejectWrongFork)
|
||||||
:andThen(function(body)
|
:andThen(function(body)
|
||||||
|
|
@ -177,6 +190,11 @@ function ApiContext:connect()
|
||||||
:andThen(rejectWrongPlaceId)
|
:andThen(rejectWrongPlaceId)
|
||||||
:andThen(function(body)
|
:andThen(function(body)
|
||||||
self.__sessionId = body.sessionId
|
self.__sessionId = body.sessionId
|
||||||
|
Log.trace(
|
||||||
|
"[TIMING] ApiContext:connect() fully validated ({:.1} ms), sessionId={}",
|
||||||
|
(os.clock() - connectClock) * 1000,
|
||||||
|
body.sessionId
|
||||||
|
)
|
||||||
|
|
||||||
return body
|
return body
|
||||||
end)
|
end)
|
||||||
|
|
@ -185,15 +203,39 @@ end
|
||||||
function ApiContext:read(ids)
|
function ApiContext:read(ids)
|
||||||
local url = ("%s/api/read/%s"):format(self.__baseUrl, table.concat(ids, ","))
|
local url = ("%s/api/read/%s"):format(self.__baseUrl, table.concat(ids, ","))
|
||||||
|
|
||||||
return Http.get(url):andThen(rejectFailedRequests):andThen(Http.Response.msgpack):andThen(function(body)
|
local readClock = os.clock()
|
||||||
if body.sessionId ~= self.__sessionId then
|
Log.trace("[TIMING] ApiContext:read() GET {} starting ({} ids)", url, #ids)
|
||||||
return Promise.reject("Server changed ID")
|
return Http.get(url)
|
||||||
end
|
:andThen(function(response)
|
||||||
|
Log.trace(
|
||||||
|
"[TIMING] ApiContext:read() HTTP response received ({:.1} ms, {} bytes)",
|
||||||
|
(os.clock() - readClock) * 1000,
|
||||||
|
if response.body then #response.body else 0
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
end)
|
||||||
|
:andThen(rejectFailedRequests)
|
||||||
|
:andThen(function(response)
|
||||||
|
local decodeClock = os.clock()
|
||||||
|
Log.trace("[TIMING] ApiContext:read() msgpack decode starting")
|
||||||
|
return Http.Response.msgpack(response):andThen(function(body)
|
||||||
|
Log.trace(
|
||||||
|
"[TIMING] ApiContext:read() msgpack decode completed ({:.1} ms)",
|
||||||
|
(os.clock() - decodeClock) * 1000
|
||||||
|
)
|
||||||
|
return body
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
:andThen(function(body)
|
||||||
|
if body.sessionId ~= self.__sessionId then
|
||||||
|
return Promise.reject("Server changed ID")
|
||||||
|
end
|
||||||
|
|
||||||
assert(validateApiRead(body))
|
assert(validateApiRead(body))
|
||||||
|
Log.trace("[TIMING] ApiContext:read() fully completed ({:.1} ms)", (os.clock() - readClock) * 1000)
|
||||||
|
|
||||||
return body
|
return body
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function ApiContext:write(patch, stageIds)
|
function ApiContext:write(patch, stageIds)
|
||||||
|
|
@ -282,11 +324,18 @@ function ApiContext:connectWebSocket(packetHandlers)
|
||||||
-- Convert HTTP/HTTPS URL to WS/WSS
|
-- Convert HTTP/HTTPS URL to WS/WSS
|
||||||
url = url:gsub("^http://", "ws://"):gsub("^https://", "wss://")
|
url = url:gsub("^http://", "ws://"):gsub("^https://", "wss://")
|
||||||
|
|
||||||
|
Log.trace("[TIMING] ApiContext:connectWebSocket() creating client for {}", url)
|
||||||
return Promise.new(function(resolve, reject)
|
return Promise.new(function(resolve, reject)
|
||||||
|
local wsClock = os.clock()
|
||||||
local success, wsClient =
|
local success, wsClient =
|
||||||
pcall(HttpService.CreateWebStreamClient, HttpService, Enum.WebStreamClientType.WebSocket, {
|
pcall(HttpService.CreateWebStreamClient, HttpService, Enum.WebStreamClientType.WebSocket, {
|
||||||
Url = url,
|
Url = url,
|
||||||
})
|
})
|
||||||
|
Log.trace(
|
||||||
|
"[TIMING] WebSocket CreateWebStreamClient ({:.1} ms, success={})",
|
||||||
|
(os.clock() - wsClock) * 1000,
|
||||||
|
success
|
||||||
|
)
|
||||||
if not success then
|
if not success then
|
||||||
reject("Failed to create WebSocket client: " .. tostring(wsClient))
|
reject("Failed to create WebSocket client: " .. tostring(wsClient))
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -1002,14 +1002,14 @@ function App:startSession()
|
||||||
self:setState({
|
self:setState({
|
||||||
connectingText = "Computing diff view...",
|
connectingText = "Computing diff view...",
|
||||||
})
|
})
|
||||||
|
local patchTreeClock = os.clock()
|
||||||
|
Log.trace("[TIMING] PatchTree.build() starting")
|
||||||
|
local patchTree =
|
||||||
|
PatchTree.build(patch, instanceMap, { "Property", "Current", "Incoming" }, serverInfo.gitMetadata)
|
||||||
|
Log.trace("[TIMING] PatchTree.build() completed ({:.1} ms)", (os.clock() - patchTreeClock) * 1000)
|
||||||
self:setState({
|
self:setState({
|
||||||
appStatus = AppStatus.Confirming,
|
appStatus = AppStatus.Confirming,
|
||||||
patchTree = PatchTree.build(
|
patchTree = patchTree,
|
||||||
patch,
|
|
||||||
instanceMap,
|
|
||||||
{ "Property", "Current", "Incoming" },
|
|
||||||
serverInfo.gitMetadata
|
|
||||||
),
|
|
||||||
confirmData = {
|
confirmData = {
|
||||||
serverInfo = serverInfo,
|
serverInfo = serverInfo,
|
||||||
},
|
},
|
||||||
|
|
@ -1021,6 +1021,7 @@ function App:startSession()
|
||||||
timeout = 7,
|
timeout = 7,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Log.trace("[TIMING] Waiting for user to confirm/reject patch...")
|
||||||
local result = self.confirmationEvent:Wait()
|
local result = self.confirmationEvent:Wait()
|
||||||
|
|
||||||
-- One-shot sync: Don't transition to Connected UI
|
-- One-shot sync: Don't transition to Connected UI
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ local reifyInstance, applyDeferredRefs = reify.reifyInstance, reify.applyDeferre
|
||||||
local setProperty = require(script.Parent.setProperty)
|
local setProperty = require(script.Parent.setProperty)
|
||||||
|
|
||||||
local function applyPatch(instanceMap, patch)
|
local function applyPatch(instanceMap, patch)
|
||||||
|
local applyPatchClock = os.clock()
|
||||||
|
|
||||||
-- Tracks any portions of the patch that could not be applied to the DOM.
|
-- Tracks any portions of the patch that could not be applied to the DOM.
|
||||||
local unappliedPatch = PatchSet.newEmpty()
|
local unappliedPatch = PatchSet.newEmpty()
|
||||||
|
|
||||||
|
|
@ -40,6 +42,14 @@ local function applyPatch(instanceMap, patch)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Log.trace(
|
||||||
|
"[TIMING] applyPatch() starting: {} removals, {} additions, {} updates",
|
||||||
|
#patch.removed,
|
||||||
|
addCount,
|
||||||
|
#patch.updated
|
||||||
|
)
|
||||||
|
|
||||||
|
local removalClock = os.clock()
|
||||||
for _, removedIdOrInstance in ipairs(patch.removed) do
|
for _, removedIdOrInstance in ipairs(patch.removed) do
|
||||||
-- Skip auto-created instances that shouldn't be synced
|
-- Skip auto-created instances that shouldn't be synced
|
||||||
local instance = if Types.RbxId(removedIdOrInstance)
|
local instance = if Types.RbxId(removedIdOrInstance)
|
||||||
|
|
@ -67,6 +77,8 @@ local function applyPatch(instanceMap, patch)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Log.trace("[TIMING] applyPatch() removals completed ({:.1} ms)", (os.clock() - removalClock) * 1000)
|
||||||
|
|
||||||
-- Detect echoed additions from DescendantAdded handler.
|
-- Detect echoed additions from DescendantAdded handler.
|
||||||
-- When the plugin pre-inserts instances with temp IDs and the server
|
-- When the plugin pre-inserts instances with temp IDs and the server
|
||||||
-- broadcasts the same instances with real Refs, remap the InstanceMap
|
-- broadcasts the same instances with real Refs, remap the InstanceMap
|
||||||
|
|
@ -100,6 +112,8 @@ local function applyPatch(instanceMap, patch)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local additionClock = os.clock()
|
||||||
|
Log.trace("[TIMING] applyPatch() additions starting")
|
||||||
for id, virtualInstance in pairs(patch.added) do
|
for id, virtualInstance in pairs(patch.added) do
|
||||||
if instanceMap.fromIds[id] ~= nil then
|
if instanceMap.fromIds[id] ~= nil then
|
||||||
-- This instance already exists. We might've already added it in a
|
-- This instance already exists. We might've already added it in a
|
||||||
|
|
@ -146,6 +160,10 @@ local function applyPatch(instanceMap, patch)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Log.trace("[TIMING] applyPatch() additions completed ({:.1} ms)", (os.clock() - additionClock) * 1000)
|
||||||
|
|
||||||
|
local updateClock = os.clock()
|
||||||
|
Log.trace("[TIMING] applyPatch() updates starting ({} updates)", #patch.updated)
|
||||||
for _, update in ipairs(patch.updated) do
|
for _, update in ipairs(patch.updated) do
|
||||||
local instance = instanceMap.fromIds[update.id]
|
local instance = instanceMap.fromIds[update.id]
|
||||||
|
|
||||||
|
|
@ -314,7 +332,14 @@ local function applyPatch(instanceMap, patch)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Log.trace("[TIMING] applyPatch() updates completed ({:.1} ms)", (os.clock() - updateClock) * 1000)
|
||||||
|
|
||||||
|
local refsClock = os.clock()
|
||||||
|
Log.trace("[TIMING] applyPatch() deferred refs starting ({} refs)", #deferredRefs)
|
||||||
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
|
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
|
||||||
|
Log.trace("[TIMING] applyPatch() deferred refs completed ({:.1} ms)", (os.clock() - refsClock) * 1000)
|
||||||
|
|
||||||
|
Log.trace("[TIMING] applyPatch() total completed ({:.1} ms)", (os.clock() - applyPatchClock) * 1000)
|
||||||
|
|
||||||
return unappliedPatch
|
return unappliedPatch
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -59,8 +59,15 @@ local function diff(instanceMap, virtualInstances, rootId, serverInfo)
|
||||||
updated = {},
|
updated = {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local diffClock = os.clock()
|
||||||
local diffCount = 0
|
local diffCount = 0
|
||||||
|
|
||||||
|
local virtualCount = 0
|
||||||
|
for _ in pairs(virtualInstances) do
|
||||||
|
virtualCount += 1
|
||||||
|
end
|
||||||
|
Log.trace("[TIMING] diff() starting ({} virtual instances to diff)", virtualCount)
|
||||||
|
|
||||||
-- Build a lookup table for visible services (for ignoreHiddenServices check)
|
-- Build a lookup table for visible services (for ignoreHiddenServices check)
|
||||||
local visibleServicesSet: { [string]: boolean } = {}
|
local visibleServicesSet: { [string]: boolean } = {}
|
||||||
if serverInfo and serverInfo.ignoreHiddenServices and serverInfo.visibleServices then
|
if serverInfo and serverInfo.ignoreHiddenServices and serverInfo.visibleServices then
|
||||||
|
|
@ -247,6 +254,19 @@ local function diff(instanceMap, virtualInstances, rootId, serverInfo)
|
||||||
return false, err
|
return false, err
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local addedCount = 0
|
||||||
|
for _ in pairs(patch.added) do
|
||||||
|
addedCount += 1
|
||||||
|
end
|
||||||
|
Log.trace(
|
||||||
|
"[TIMING] diff() completed: {} instances diffed, {} removals, {} additions, {} updates ({:.1} ms)",
|
||||||
|
diffCount,
|
||||||
|
#patch.removed,
|
||||||
|
addedCount,
|
||||||
|
#patch.updated,
|
||||||
|
(os.clock() - diffClock) * 1000
|
||||||
|
)
|
||||||
|
|
||||||
return true, patch
|
return true, patch
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,15 @@ local Matching = require(script.Parent.matching)
|
||||||
local HYDRATE_YIELD_INTERVAL = 1000
|
local HYDRATE_YIELD_INTERVAL = 1000
|
||||||
|
|
||||||
local function hydrate(instanceMap, virtualInstances, rootId, rootInstance, session)
|
local function hydrate(instanceMap, virtualInstances, rootId, rootInstance, session)
|
||||||
|
local isRoot = not session or not session.hydrateCount
|
||||||
|
|
||||||
if not session then
|
if not session then
|
||||||
session = Matching.newSession()
|
session = Matching.newSession()
|
||||||
end
|
end
|
||||||
|
|
||||||
if not session.hydrateCount then
|
if not session.hydrateCount then
|
||||||
session.hydrateCount = 0
|
session.hydrateCount = 0
|
||||||
|
session.hydrateClock = os.clock()
|
||||||
end
|
end
|
||||||
session.hydrateCount += 1
|
session.hydrateCount += 1
|
||||||
if session.hydrateCount % HYDRATE_YIELD_INTERVAL == 0 then
|
if session.hydrateCount % HYDRATE_YIELD_INTERVAL == 0 then
|
||||||
|
|
@ -34,6 +37,10 @@ local function hydrate(instanceMap, virtualInstances, rootId, rootInstance, sess
|
||||||
invariant("Cannot hydrate an instance not present in virtualInstances\nID: {}", rootId)
|
invariant("Cannot hydrate an instance not present in virtualInstances\nID: {}", rootId)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if isRoot then
|
||||||
|
Log.trace("[TIMING] hydrate() starting at root '{}' ({})", virtualInstance.Name, rootInstance:GetFullName())
|
||||||
|
end
|
||||||
|
|
||||||
instanceMap:insert(rootId, rootInstance)
|
instanceMap:insert(rootId, rootInstance)
|
||||||
|
|
||||||
local existingChildren = rootInstance:GetChildren()
|
local existingChildren = rootInstance:GetChildren()
|
||||||
|
|
@ -54,8 +61,21 @@ local function hydrate(instanceMap, virtualInstances, rootId, rootInstance, sess
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local matchClock = os.clock()
|
||||||
local result =
|
local result =
|
||||||
Matching.matchChildren(session, validVirtualIds, existingChildren, virtualInstances, rootId, rootInstance)
|
Matching.matchChildren(session, validVirtualIds, existingChildren, virtualInstances, rootId, rootInstance)
|
||||||
|
local matchElapsed = (os.clock() - matchClock) * 1000
|
||||||
|
|
||||||
|
if matchElapsed > 10 then
|
||||||
|
Log.trace(
|
||||||
|
"[TIMING] matchChildren for '{}' took {:.1} ms ({} virtual, {} studio, {} matched)",
|
||||||
|
virtualInstance.Name,
|
||||||
|
matchElapsed,
|
||||||
|
#validVirtualIds,
|
||||||
|
#existingChildren,
|
||||||
|
#result.matched
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
-- Recursively hydrate matched pairs
|
-- Recursively hydrate matched pairs
|
||||||
for _, pair in result.matched do
|
for _, pair in result.matched do
|
||||||
|
|
@ -75,6 +95,15 @@ local function hydrate(instanceMap, virtualInstances, rootId, rootInstance, sess
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if isRoot then
|
||||||
|
Log.trace(
|
||||||
|
"[TIMING] hydrate() completed: {} instances processed, {} matched ({:.1} ms total)",
|
||||||
|
session.hydrateCount,
|
||||||
|
#result.matched,
|
||||||
|
(os.clock() - session.hydrateClock) * 1000
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return hydrate
|
return hydrate
|
||||||
|
|
|
||||||
|
|
@ -329,9 +329,13 @@ function ServeSession:start()
|
||||||
self:__setStatus(Status.Connecting)
|
self:__setStatus(Status.Connecting)
|
||||||
self:setLoadingText("Connecting to server...")
|
self:setLoadingText("Connecting to server...")
|
||||||
|
|
||||||
|
local sessionStartClock = os.clock()
|
||||||
|
Log.trace("[TIMING] ServeSession:start() begun")
|
||||||
|
|
||||||
self.__apiContext
|
self.__apiContext
|
||||||
:connect()
|
:connect()
|
||||||
:andThen(function(serverInfo)
|
:andThen(function(serverInfo)
|
||||||
|
Log.trace("[TIMING] ApiContext:connect() completed ({:.1} ms)", (os.clock() - sessionStartClock) * 1000)
|
||||||
self:setLoadingText("Loading initial data from server...")
|
self:setLoadingText("Loading initial data from server...")
|
||||||
|
|
||||||
-- Configure sync mode based on server capabilities
|
-- Configure sync mode based on server capabilities
|
||||||
|
|
@ -342,9 +346,18 @@ function ServeSession:start()
|
||||||
self.__changeBatcher:setSyncScriptsOnly(self.__syncScriptsOnly)
|
self.__changeBatcher:setSyncScriptsOnly(self.__syncScriptsOnly)
|
||||||
|
|
||||||
self.__serverInfo = serverInfo
|
self.__serverInfo = serverInfo
|
||||||
|
|
||||||
|
local computePatchClock = os.clock()
|
||||||
|
Log.trace("[TIMING] __computeInitialPatch() starting")
|
||||||
return self:__computeInitialPatch(serverInfo):andThen(function(catchUpPatch)
|
return self:__computeInitialPatch(serverInfo):andThen(function(catchUpPatch)
|
||||||
|
Log.trace(
|
||||||
|
"[TIMING] __computeInitialPatch() completed ({:.1} ms)",
|
||||||
|
(os.clock() - computePatchClock) * 1000
|
||||||
|
)
|
||||||
self:setLoadingText("Starting sync loop...")
|
self:setLoadingText("Starting sync loop...")
|
||||||
|
|
||||||
|
local wsClock = os.clock()
|
||||||
|
Log.trace("[TIMING] connectWebSocket() starting")
|
||||||
-- Connect WebSocket BEFORE showing confirmation so changes
|
-- Connect WebSocket BEFORE showing confirmation so changes
|
||||||
-- during confirmation can be merged into the patch
|
-- during confirmation can be merged into the patch
|
||||||
-- Note: connectWebSocket returns a Promise that only resolves when
|
-- Note: connectWebSocket returns a Promise that only resolves when
|
||||||
|
|
@ -360,12 +373,22 @@ function ServeSession:start()
|
||||||
self:__stopInternal(err)
|
self:__stopInternal(err)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
Log.trace("[TIMING] connectWebSocket() initiated ({:.1} ms)", (os.clock() - wsClock) * 1000)
|
||||||
|
|
||||||
|
local confirmClock = os.clock()
|
||||||
|
Log.trace("[TIMING] __confirmAndApplyInitialPatch() starting")
|
||||||
-- Now show confirmation and wait for user decision
|
-- Now show confirmation and wait for user decision
|
||||||
return self:__confirmAndApplyInitialPatch(catchUpPatch, serverInfo)
|
return self:__confirmAndApplyInitialPatch(catchUpPatch, serverInfo):andThen(function(...)
|
||||||
|
Log.trace(
|
||||||
|
"[TIMING] __confirmAndApplyInitialPatch() completed ({:.1} ms)",
|
||||||
|
(os.clock() - confirmClock) * 1000
|
||||||
|
)
|
||||||
|
return ...
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
:andThen(function()
|
:andThen(function()
|
||||||
|
Log.trace("[TIMING] ServeSession:start() total elapsed ({:.1} ms)", (os.clock() - sessionStartClock) * 1000)
|
||||||
-- Initial sync is fully complete (including any writes to the server)
|
-- Initial sync is fully complete (including any writes to the server)
|
||||||
if self.__initialSyncCompleteCallback ~= nil then
|
if self.__initialSyncCompleteCallback ~= nil then
|
||||||
self.__initialSyncCompleteCallback()
|
self.__initialSyncCompleteCallback()
|
||||||
|
|
@ -568,7 +591,14 @@ function ServeSession:__applyPatch(patch)
|
||||||
Timer.stop()
|
Timer.stop()
|
||||||
|
|
||||||
self.__applyingPatch = true
|
self.__applyingPatch = true
|
||||||
|
local reconcilerClock = os.clock()
|
||||||
|
Log.trace("[TIMING] reconciler:applyPatch() starting")
|
||||||
local patchApplySuccess, unappliedPatch = pcall(self.__reconciler.applyPatch, self.__reconciler, patch)
|
local patchApplySuccess, unappliedPatch = pcall(self.__reconciler.applyPatch, self.__reconciler, patch)
|
||||||
|
Log.trace(
|
||||||
|
"[TIMING] reconciler:applyPatch() completed ({:.1} ms, success={})",
|
||||||
|
(os.clock() - reconcilerClock) * 1000,
|
||||||
|
patchApplySuccess
|
||||||
|
)
|
||||||
if not patchApplySuccess then
|
if not patchApplySuccess then
|
||||||
self.__applyingPatch = false
|
self.__applyingPatch = false
|
||||||
if historyRecording then
|
if historyRecording then
|
||||||
|
|
@ -579,8 +609,10 @@ function ServeSession:__applyPatch(patch)
|
||||||
error(unappliedPatch)
|
error(unappliedPatch)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local fallbackClock = os.clock()
|
||||||
local fallbackOk, fallbackErr = pcall(function()
|
local fallbackOk, fallbackErr = pcall(function()
|
||||||
if Settings:get("enableSyncFallback") and not PatchSet.isEmpty(unappliedPatch) then
|
if Settings:get("enableSyncFallback") and not PatchSet.isEmpty(unappliedPatch) then
|
||||||
|
Log.trace("[TIMING] Sync fallback starting (unapplied patch is non-empty)")
|
||||||
-- Some changes did not apply, let's try replacing them instead
|
-- Some changes did not apply, let's try replacing them instead
|
||||||
local addedIdList = PatchSet.addedIdList(unappliedPatch)
|
local addedIdList = PatchSet.addedIdList(unappliedPatch)
|
||||||
local updatedIdList = PatchSet.updatedIdList(unappliedPatch)
|
local updatedIdList = PatchSet.updatedIdList(unappliedPatch)
|
||||||
|
|
@ -607,6 +639,7 @@ function ServeSession:__applyPatch(patch)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
self.__applyingPatch = false
|
self.__applyingPatch = false
|
||||||
|
Log.trace("[TIMING] Sync fallback phase completed ({:.1} ms)", (os.clock() - fallbackClock) * 1000)
|
||||||
|
|
||||||
if not fallbackOk then
|
if not fallbackOk then
|
||||||
Log.warn("Sync fallback errored: {}", fallbackErr)
|
Log.warn("Sync fallback errored: {}", fallbackErr)
|
||||||
|
|
@ -638,7 +671,17 @@ function ServeSession:__applyPatch(patch)
|
||||||
end
|
end
|
||||||
|
|
||||||
function ServeSession:__computeInitialPatch(serverInfo)
|
function ServeSession:__computeInitialPatch(serverInfo)
|
||||||
|
local readClock = os.clock()
|
||||||
|
Log.trace("[TIMING] ApiContext:read() starting")
|
||||||
return self.__apiContext:read({ serverInfo.rootInstanceId }):andThen(function(readResponseBody)
|
return self.__apiContext:read({ serverInfo.rootInstanceId }):andThen(function(readResponseBody)
|
||||||
|
Log.trace("[TIMING] ApiContext:read() completed ({:.1} ms)", (os.clock() - readClock) * 1000)
|
||||||
|
|
||||||
|
local instanceCount = 0
|
||||||
|
for _ in pairs(readResponseBody.instances) do
|
||||||
|
instanceCount += 1
|
||||||
|
end
|
||||||
|
Log.trace("[TIMING] Read response contains {} instances", instanceCount)
|
||||||
|
|
||||||
-- Tell the API Context that we're up-to-date with the version of
|
-- Tell the API Context that we're up-to-date with the version of
|
||||||
-- the tree defined in this response.
|
-- the tree defined in this response.
|
||||||
self.__apiContext:setMessageCursor(readResponseBody.messageCursor)
|
self.__apiContext:setMessageCursor(readResponseBody.messageCursor)
|
||||||
|
|
@ -647,20 +690,43 @@ function ServeSession:__computeInitialPatch(serverInfo)
|
||||||
-- tracking them in the reconciler.
|
-- tracking them in the reconciler.
|
||||||
Log.trace("Matching existing Roblox instances to Rojo IDs")
|
Log.trace("Matching existing Roblox instances to Rojo IDs")
|
||||||
self:setLoadingText("Hydrating instance map...")
|
self:setLoadingText("Hydrating instance map...")
|
||||||
|
local hydrateClock = os.clock()
|
||||||
|
Log.trace("[TIMING] reconciler:hydrate() starting ({} virtual instances)", instanceCount)
|
||||||
local matchingSession = Matching.newSession()
|
local matchingSession = Matching.newSession()
|
||||||
self.__reconciler:hydrate(readResponseBody.instances, serverInfo.rootInstanceId, game, matchingSession)
|
self.__reconciler:hydrate(readResponseBody.instances, serverInfo.rootInstanceId, game, matchingSession)
|
||||||
|
Log.trace("[TIMING] reconciler:hydrate() completed ({:.1} ms)", (os.clock() - hydrateClock) * 1000)
|
||||||
|
|
||||||
|
local hydratedCount = 0
|
||||||
|
for _ in pairs(self.__instanceMap.fromIds) do
|
||||||
|
hydratedCount += 1
|
||||||
|
end
|
||||||
|
Log.trace("[TIMING] InstanceMap contains {} entries after hydration", hydratedCount)
|
||||||
|
|
||||||
-- Calculate the initial patch to apply to the DataModel to catch us
|
-- Calculate the initial patch to apply to the DataModel to catch us
|
||||||
-- up to what Rojo thinks the place should look like.
|
-- up to what Rojo thinks the place should look like.
|
||||||
Log.trace("Computing changes that plugin needs to make to catch up to server...")
|
Log.trace("Computing changes that plugin needs to make to catch up to server...")
|
||||||
self:setLoadingText("Finding differences between server and Studio...")
|
self:setLoadingText("Finding differences between server and Studio...")
|
||||||
|
local diffClock = os.clock()
|
||||||
|
Log.trace("[TIMING] reconciler:diff() starting")
|
||||||
local success, catchUpPatch =
|
local success, catchUpPatch =
|
||||||
self.__reconciler:diff(readResponseBody.instances, serverInfo.rootInstanceId, serverInfo)
|
self.__reconciler:diff(readResponseBody.instances, serverInfo.rootInstanceId, serverInfo)
|
||||||
|
Log.trace("[TIMING] reconciler:diff() completed ({:.1} ms)", (os.clock() - diffClock) * 1000)
|
||||||
|
|
||||||
if not success then
|
if not success then
|
||||||
Log.error("Could not compute a diff to catch up to the Rojo server: {:#?}", catchUpPatch)
|
Log.error("Could not compute a diff to catch up to the Rojo server: {:#?}", catchUpPatch)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local addedCount = 0
|
||||||
|
for _ in pairs(catchUpPatch.added) do
|
||||||
|
addedCount += 1
|
||||||
|
end
|
||||||
|
Log.trace(
|
||||||
|
"[TIMING] Diff result: {} removals, {} additions, {} updates",
|
||||||
|
#catchUpPatch.removed,
|
||||||
|
addedCount,
|
||||||
|
#catchUpPatch.updated
|
||||||
|
)
|
||||||
|
|
||||||
for _, update in catchUpPatch.updated do
|
for _, update in catchUpPatch.updated do
|
||||||
if update.id == self.__instanceMap.fromInstances[game] and update.changedClassName ~= nil then
|
if update.id == self.__instanceMap.fromInstances[game] and update.changedClassName ~= nil then
|
||||||
-- Non-place projects will try to update the classname of game from DataModel to
|
-- Non-place projects will try to update the classname of game from DataModel to
|
||||||
|
|
@ -690,7 +756,14 @@ function ServeSession:__confirmAndApplyInitialPatch(catchUpPatch, serverInfo)
|
||||||
self.__isConfirming = true
|
self.__isConfirming = true
|
||||||
self.__confirmingPatch = catchUpPatch
|
self.__confirmingPatch = catchUpPatch
|
||||||
|
|
||||||
|
local confirmWaitClock = os.clock()
|
||||||
|
Log.trace("[TIMING] Waiting for user confirmation...")
|
||||||
userDecision = self.__userConfirmCallback(self.__instanceMap, catchUpPatch, serverInfo)
|
userDecision = self.__userConfirmCallback(self.__instanceMap, catchUpPatch, serverInfo)
|
||||||
|
Log.trace(
|
||||||
|
"[TIMING] User confirmation received: {} ({:.1} ms wait)",
|
||||||
|
tostring(userDecision),
|
||||||
|
(os.clock() - confirmWaitClock) * 1000
|
||||||
|
)
|
||||||
|
|
||||||
-- Clear confirmation state flags
|
-- Clear confirmation state flags
|
||||||
self.__confirmingPatch = nil
|
self.__confirmingPatch = nil
|
||||||
|
|
@ -713,7 +786,10 @@ function ServeSession:__confirmAndApplyInitialPatch(catchUpPatch, serverInfo)
|
||||||
-- Legacy: Accept all changes (push all to Studio)
|
-- Legacy: Accept all changes (push all to Studio)
|
||||||
self:__setStatus(Status.Connected, serverInfo.projectName)
|
self:__setStatus(Status.Connected, serverInfo.projectName)
|
||||||
self:__applyGameAndPlaceId(serverInfo)
|
self:__applyGameAndPlaceId(serverInfo)
|
||||||
|
local applyPatchClock = os.clock()
|
||||||
|
Log.trace("[TIMING] __applyPatch() starting (Accept path)")
|
||||||
self:__applyPatch(catchUpPatch)
|
self:__applyPatch(catchUpPatch)
|
||||||
|
Log.trace("[TIMING] __applyPatch() completed ({:.1} ms)", (os.clock() - applyPatchClock) * 1000)
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
elseif type(userDecision) == "table" and userDecision.type == "Confirm" then
|
elseif type(userDecision) == "table" and userDecision.type == "Confirm" then
|
||||||
-- New: Apply based on per-item selections
|
-- New: Apply based on per-item selections
|
||||||
|
|
@ -881,7 +957,10 @@ function ServeSession:__confirmAndApplyInitialPatch(catchUpPatch, serverInfo)
|
||||||
#pushPatch.removed,
|
#pushPatch.removed,
|
||||||
#pushPatch.updated
|
#pushPatch.updated
|
||||||
)
|
)
|
||||||
|
local pushClock = os.clock()
|
||||||
|
Log.trace("[TIMING] __applyPatch() starting (Confirm push path)")
|
||||||
self:__applyPatch(pushPatch)
|
self:__applyPatch(pushPatch)
|
||||||
|
Log.trace("[TIMING] __applyPatch() completed ({:.1} ms)", (os.clock() - pushClock) * 1000)
|
||||||
end
|
end
|
||||||
|
|
||||||
if Settings:get("oneShotSync") then
|
if Settings:get("oneShotSync") then
|
||||||
|
|
@ -908,7 +987,12 @@ function ServeSession:__confirmAndApplyInitialPatch(catchUpPatch, serverInfo)
|
||||||
if hasStageIds then
|
if hasStageIds then
|
||||||
Log.info("Requesting git stage for {} files", #stageIds)
|
Log.info("Requesting git stage for {} files", #stageIds)
|
||||||
end
|
end
|
||||||
return self.__apiContext:write(pullPatch, stageIds)
|
local writeClock = os.clock()
|
||||||
|
Log.trace("[TIMING] ApiContext:write() starting (pull patch)")
|
||||||
|
return self.__apiContext:write(pullPatch, stageIds):andThen(function(...)
|
||||||
|
Log.trace("[TIMING] ApiContext:write() completed ({:.1} ms)", (os.clock() - writeClock) * 1000)
|
||||||
|
return ...
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue