Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use GetObjects as fallback for instances that can't be created normally #401

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ regex = "1.3.1"
reqwest = "0.9.20"
ritz = "0.1.0"
rlua = "0.17.0"
roblox_install = "0.2.2"
roblox_install = "0.3.0"
serde = { version = "1.0", features = ["derive", "rc"] }
serde_json = "1.0"
structopt = "0.3.5"
Expand Down
2 changes: 1 addition & 1 deletion plugin/modules/promise
Submodule promise updated 4 files
+7 −2 README.md
+174 −58 lib/init.lua
+211 −50 lib/init.spec.lua
+7 −0 rotriever.toml
2 changes: 1 addition & 1 deletion plugin/modules/testez
Submodule testez updated 104 files
16 changes: 16 additions & 0 deletions plugin/src/ApiContext.lua
Original file line number Diff line number Diff line change
Expand Up @@ -248,4 +248,20 @@ function ApiContext:open(id)
end)
end

function ApiContext:createAssets(assets)
local url = ("%s/api/create-assets"):format(self.__baseUrl)
local body = Http.jsonEncode({
sessionId = self.__sessionId,
assets = assets,
})

return Http.post(url, body)
:andThen(rejectFailedRequests)
:andThen(Http.Response.json)
:andThen(function(responseBody)
Log.trace("createAssets response: {:#?}", responseBody)
return responseBody.url
end)
end

return ApiContext
3 changes: 2 additions & 1 deletion plugin/src/Config.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
local strict = require(script.Parent.strict)

local isDevBuild = script.Parent.Parent:FindFirstChild("ROJO_DEV_BUILD") ~= nil
-- local isDevBuild = script.Parent.Parent:FindFirstChild("ROJO_DEV_BUILD") ~= nil
local isDevBuild = true

return strict("Config", {
isDevBuild = isDevBuild,
Expand Down
32 changes: 25 additions & 7 deletions plugin/src/ServeSession.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ local t = require(script.Parent.Parent.t)

local InstanceMap = require(script.Parent.InstanceMap)
local PatchSet = require(script.Parent.PatchSet)
local Promise = require(script.Parent.Parent.Promise)
local Reconciler = require(script.Parent.Reconciler)
local strict = require(script.Parent.strict)
local tryGetObjects = require(script.Parent.tryGetObjects)

local Status = strict("Session.Status", {
NotStarted = "NotStarted",
Expand Down Expand Up @@ -242,27 +244,43 @@ function ServeSession:__initialSync(rootInstanceId)
local unappliedPatch = self.__reconciler:applyPatch(catchUpPatch)

if not PatchSet.isEmpty(unappliedPatch) then
Log.warn("Could not apply all changes requested by the Rojo server:\n{}",
PatchSet.humanSummary(self.__instanceMap, unappliedPatch))
return tryGetObjects(self.__instanceMap, self.__apiContext, unappliedPatch)
:andThen(function(finallyUnappliedPatch)
if not PatchSet.isEmpty(finallyUnappliedPatch) then
Log.warn("Could not apply all changes requested by the Rojo server:\n{}",
PatchSet.humanSummary(self.__instanceMap, finallyUnappliedPatch))
end
end)
end
end)
end

function ServeSession:__mainSyncLoop()
return self.__apiContext:retrieveMessages()
:andThen(function(messages)
local getObjectsPromises = {}

for _, message in ipairs(messages) do
local unappliedPatch = self.__reconciler:applyPatch(message)

if not PatchSet.isEmpty(unappliedPatch) then
Log.warn("Could not apply all changes requested by the Rojo server:\n{}",
PatchSet.humanSummary(self.__instanceMap, unappliedPatch))
table.insert(getObjectsPromises,
tryGetObjects(self.__instanceMap, self.__apiContext, unappliedPatch)
:andThen(function(finallyUnappliedPatch)
if not PatchSet.isEmpty(finallyUnappliedPatch) then
Log.warn("Could not apply all changes requested by the Rojo server:\n{}",
PatchSet.humanSummary(self.__instanceMap, finallyUnappliedPatch))
end
end)
)
end
end

if self.__status ~= Status.Disconnected then
return self:__mainSyncLoop()
end
return Promise.all(getObjectsPromises):andThen(function()
if self.__status ~= Status.Disconnected then
return self:__mainSyncLoop()
end
end)
end)
end

Expand Down
8 changes: 4 additions & 4 deletions plugin/src/init.server.lua
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ plugin.Unloading:Connect(function()
Roact.unmount(tree)
end)

if Config.isDevBuild then
local TestEZ = require(script.Parent.TestEZ)
-- if Config.isDevBuild then
-- local TestEZ = require(script.Parent.TestEZ)

require(script.runTests)(TestEZ)
end
-- require(script.runTests)(TestEZ)
-- end
92 changes: 92 additions & 0 deletions plugin/src/tryGetObjects.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
local Fmt = require(script.Parent.Parent.Fmt)
local Log = require(script.Parent.Parent.Log)
local PatchSet = require(script.Parent.PatchSet)
local Promise = require(script.Parent.Parent.Promise)

local function tryGetObjects(instanceMap, apiContext, patch)
assert(PatchSet.validate(patch))

local unappliedPatch = PatchSet.newEmpty()

-- GetObjects won't help with anything that failed to remove
unappliedPatch.removed = patch.removed
-- TODO: Implement this? Do we have to?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! This is important for if a file is newly added on the filesystem

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Figured, just couldn't figure it out because I was using existing projects, and wasn't creating new files.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied and pasted a file and...it worked perfectly? It was still under updated, not added.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, added seems to be used when modifying .project.json.

unappliedPatch.added = patch.added

local assetsToRequest = {}
local receiveCallbacks = {}

Log.trace("tryGetObjects({:#?})", patch)

-- TODO: added?

-- GetObjects only create instances, we can't update the properties of existing ones.
-- Instead, just create them again, move their children, and replace the instance.
for _, update in ipairs(patch.updated) do
-- If no properties were changed during an update, GetObjects isn't going to do anything that hasn't already been tried
if next(update.changedProperties) == nil then
continue
end

table.insert(assetsToRequest, update.id)
table.insert(unappliedPatch.updated, update)

receiveCallbacks[update.id] = function(newInstance)
table.remove(unappliedPatch.updated, table.find(unappliedPatch.updated, update))

local oldInstance = instanceMap.fromIds[update.id]

-- TODO: What if oldInstance is nil?
Kampfkarren marked this conversation as resolved.
Show resolved Hide resolved
for _, oldChild in ipairs(oldInstance:GetChildren()) do
oldChild.Parent = newInstance
end

local oldParent = oldInstance.Parent
instanceMap:destroyInstance(oldInstance)
newInstance.Parent = oldParent
Kampfkarren marked this conversation as resolved.
Show resolved Hide resolved
end
end

if #assetsToRequest == 0 then
return Promise.resolve(unappliedPatch)
end

return apiContext:createAssets(assetsToRequest):andThen(function(assetId)
--[[
The assets Rojo creates that we will be loading is in the following structure:

Assuming we requested the following IDs:
- DOGE: A doge mesh named Doge
- ROCK: A rock mesh named MyPet

Root: Folder
* DOGE: Folder
* Doge: MeshPart
* ROCK: Folder
* MyPet: MeshPart
]]
Kampfkarren marked this conversation as resolved.
Show resolved Hide resolved

-- GetObjects doesn't yield, it blocks.
-- There is no way to promisify this unless GetObjectsAsync is opened up.
local createdAssets = game:GetObjects(assetId)[1]

if createdAssets == nil then
Log.warn("Request to create assets returned an asset that was empty.")
return unappliedPatch
end

for _, assetFolder in ipairs(createdAssets:GetChildren()) do
local requestedId = assetFolder.Name

-- TODO: Does this need to support multi-rooted instances? Probably not?
local assetInstance = assetFolder:GetChildren()[1]

receiveCallbacks[requestedId](assetInstance)
instanceMap:insert(requestedId, assetInstance)
end

return unappliedPatch
end)
end

return tryGetObjects
39 changes: 39 additions & 0 deletions plugin/src/tryGetObjects.spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
-- TODO: Have some documentation/script in order to force the rbxassets to be there
Kampfkarren marked this conversation as resolved.
Show resolved Hide resolved
return function()
local InstanceMap = require(script.Parent.InstanceMap)
local PatchSet = require(script.Parent.PatchSet)
local Promise = require(script.Parent.Parent.Promise)
local tryGetObjects = require(script.Parent.tryGetObjects)

local MESH_DOGE = "rbxassetid://4574885352"

it("should apply updates to existing instances", function()
local mockApiContext = {}
function mockApiContext:createAssets()
return Promise.resolve("rbxasset://rojo-tests/DogeUpdate.rbxm")
end

local instanceMap = InstanceMap.new()

local oldDoge = Instance.new("MeshPart")
instanceMap:insert("DOGE", oldDoge)

local patch = PatchSet.newEmpty()
table.insert(patch.updated, {
id = "DOGE",
changedProperties = {
MeshId = {
Type = "Content",
Value = MESH_DOGE,
},
},
})

local _, unappliedPatch = assert(tryGetObjects(instanceMap, mockApiContext, patch):await())
assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")

local newDoge = instanceMap.fromIds["DOGE"]
assert(newDoge ~= nil, "no instance with id DOGE")
expect(newDoge.MeshId).to.equal(MESH_DOGE)
end)
end
2 changes: 1 addition & 1 deletion plugin/test-place.project.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
},

"TestEZ": {
"$path": "modules/testez/lib"
"$path": "modules/testez/src"
}
},

Expand Down
2 changes: 2 additions & 0 deletions plugin/test.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
rojo build test-place.project.json -o TestPlace.rbxlx
run-in-roblox --script run-tests.server.lua --place TestPlace.rbxlx
2 changes: 1 addition & 1 deletion selene.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
std = "roblox"
std = "roblox+testez"

[config]
unused_variable = { allow_unused_self = true }
Loading