Skip to content

Commit

Permalink
Roblox renderer v2 (#54)
Browse files Browse the repository at this point in the history
Starting to stand up the logic for the Roblox renderer, something much closer to what we'll expect to see in the final project. For now, I've dragged a bunch of placeholder stuff over to try to get a sense of what the interface will look like.

Ultimately, this involves a lot of roughly-translated pieces of ReactDOM, so that we can get certain abstractions that form the public API of ReactDOM, something we'd probably like to mimic either way ("roots" is a good example).

Nearly all of the lifted Roact code brought its unit tests with it (bindings, events, etc.). We may want to promote bindings back up to the reconciler at some point, and maybe even propose them as an upstream feature someday!
  • Loading branch information
RoFlection Bot committed Feb 19, 2021
1 parent 74aa9ad commit 3249d0c
Show file tree
Hide file tree
Showing 84 changed files with 4,734 additions and 123 deletions.
4 changes: 2 additions & 2 deletions bin/ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

set -x

rojo build --output model.rbxmx
roblox-cli analyze default.project.json
rojo build tests.project.json --output model.rbxmx
roblox-cli analyze tests.project.json
selene --version
selene --config selene.toml --pattern "**/*[a-bd-z].lua" modules/
echo "Run tests in DEV"
Expand Down
4 changes: 2 additions & 2 deletions bin/spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ local Root = script.Parent.RoactAlignment

-- Load RoactNavigation source into Packages folder so it's next to Roact as expected
local TestEZ = require(Root.Packages.Dev.TestEZ)
local RobloxJest = require(Root.Modules.RobloxJest)
local RobloxJest = require(Root.Packages.Modules.RobloxJest)

-- Run all tests, collect results, and report to stdout.
TestEZ.TestBootstrap:run(
{ Root.Modules },
{ Root.Packages.Modules },
TestEZ.Reporters.TextReporterQuiet,
{ extraEnvironment = RobloxJest.testEnv }
)
35 changes: 1 addition & 34 deletions default.project.json
Original file line number Diff line number Diff line change
@@ -1,39 +1,6 @@
{
"name": "RoactAlignment",
"tree": {
"$className": "Folder",
"Packages": {
"$path": "Packages"
},
"Modules": {
"$className": "Folder",
"React": {
"$path": "modules/react/src"
},
"ReactIs": {
"$path": "modules/react-is/src"
},
"ReactReconciler": {
"$path": "modules/react-reconciler/src"
},
"ReactNoopRenderer": {
"$path": "modules/react-noop-renderer/src"
},
"ReactShallowRenderer": {
"$path": "modules/react-shallow-renderer/src"
},
"ReactTestRenderer": {
"$path": "modules/react-test-renderer/src"
},
"RobloxJest": {
"$path": "modules/roblox-jest/src"
},
"Scheduler": {
"$path": "modules/scheduler/src"
},
"Shared": {
"$path": "modules/shared/src"
}
}
"$path": "modules"
}
}
33 changes: 33 additions & 0 deletions examples.project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "Roact Test Place",
"tree": {
"$className": "DataModel",

"ReplicatedStorage": {
"$className": "ReplicatedStorage",
"$path": "Packages",
"Roact": {
"$path": "modules"
}
},

"StarterPlayer": {
"$className": "StarterPlayer",

"StarterPlayerScripts": {
"$className": "StarterPlayerScripts",

"RoactExamples": {
"$path": "examples"
}
}
},

"Players": {
"$className": "Players",
"$properties": {
"CharacterAutoLoads": false
}
}
}
}
69 changes: 69 additions & 0 deletions examples/binding/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
return function()
local LocalPlayer = game:GetService("Players").LocalPlayer
local PlayerGui = LocalPlayer.PlayerGui
local Mouse = LocalPlayer:GetMouse()

local COUNT = 500
local Roact = require(game.ReplicatedStorage.Roact)

local BindingExample = Roact.Component:extend("BindingExample")

local function Follower(props)
return Roact.createElement("Frame", {
Size = UDim2.fromOffset(12, 12),
AnchorPoint = Vector2.new(0.5, 0.5),
Position = props.position,
BackgroundColor3 = props.color,
})
end

function BindingExample:init()
self.binding, self.updateBinding = Roact.createBinding({})
end

function BindingExample:render()
local followers = {}
for i = 0, COUNT do
followers[i] = Roact.createElement(Follower, {
position = self.binding:map(function(lastPositions)
return lastPositions[i]
end),
color = Color3.new(i / COUNT, 0, i / COUNT),
})
end

return followers
end

function BindingExample:componentDidMount()
local incrementor = 1

self.mouseConnection = Mouse.Move:Connect(function()
local positions = self.binding:getValue()
positions[incrementor % COUNT + 1] = UDim2.fromOffset(Mouse.X, Mouse.Y)
self.updateBinding(positions)
incrementor += 1
end)
end

function BindingExample:componentWillUnmount()
self.mouseConnection:Disconnect()
end

local app = Roact.createElement("ScreenGui", nil, {
BindingExample = Roact.createElement(BindingExample),
})

local rootInstance = Instance.new("Folder")
rootInstance.Parent = PlayerGui

local root = Roact.createBlockingRoot(rootInstance)
root:render(app)

local function stop()
root:unmount()
rootInstance.Parent = nil
end

return stop
end
90 changes: 90 additions & 0 deletions examples/changed-signal/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
return function()
local PlayerGui = game:GetService("Players").LocalPlayer.PlayerGui

local Roact = require(game.ReplicatedStorage.Roact)

--[[
A TextBox that the user can type into. Takes a callback to be
triggered when text changes.
]]
local function InputTextBox(props)
local onTextChanged = props.onTextChanged
local layoutOrder = props.layoutOrder

return Roact.createElement("TextBox", {
LayoutOrder = layoutOrder,
Text = "Type Here!",
Size = UDim2.new(1, 0, 0.5, 0),
[Roact.Change.Text] = onTextChanged,
})
end

--[[
A TextLabel that display the given text in reverse.
]]
local function ReversedText(props)
local inputText = props.inputText
local layoutOrder = props.layoutOrder

return Roact.createElement("TextLabel", {
LayoutOrder = layoutOrder,
Size = UDim2.new(1, 0, 0.5, 0),
Text = "Reversed: " .. inputText:reverse(),
})
end

--[[
Displays a TextBox and a TextLabel that shows the reverse of
the TextBox's input in real time
]]
local TextReverser = Roact.Component:extend("TextReverser")

function TextReverser:init()
self.state = {
text = "",
}
end

function TextReverser:render()
local text = self.state.text

return Roact.createElement("Frame", {
Size = UDim2.new(0, 400, 0, 400),
Position = UDim2.new(0.5, 0, 0.5, 0),
AnchorPoint = Vector2.new(0.5, 0.5),
}, {
Roact.createElement("UIListLayout", {
SortOrder = Enum.SortOrder.LayoutOrder,
}),
Roact.createElement(InputTextBox, {
layoutOrder = 1,
onTextChanged = function(rbx)
self:setState({
text = rbx.Text or "",
})
end,
}),
Roact.createElement(ReversedText, {
layoutOrder = 2,
inputText = text,
}),
})
end

local app = Roact.createElement("ScreenGui", nil, {
TextReverser = Roact.createElement(TextReverser),
})

local rootInstance = Instance.new("Folder")
rootInstance.Parent = PlayerGui

local root = Roact.createBlockingRoot(rootInstance)
root:render(app)

local function stop()
root:unmount()
rootInstance.Parent = nil
end

return stop
end
48 changes: 48 additions & 0 deletions examples/clock/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
return function()
local PlayerGui = game:GetService("Players").LocalPlayer.PlayerGui

local Roact = require(game.ReplicatedStorage.Roact)

local function ClockApp(props)
local timeValue = props.time

return Roact.createElement("ScreenGui", nil, {
Main = Roact.createElement("TextLabel", {
Size = UDim2.new(0, 400, 0, 300),
Position = UDim2.new(0.5, 0, 0.5, 0),
AnchorPoint = Vector2.new(0.5, 0.5),
Text = "The current time is: " .. timeValue,
}),
})
end

local rootInstance = Instance.new("Folder")
rootInstance.Parent = PlayerGui

local running = true
local currentTime = 0
local root = Roact.createBlockingRoot(rootInstance)
root:render(Roact.createElement(ClockApp, {
time = currentTime,
}))

spawn(function()
while running do
currentTime = currentTime + 1

root:render(Roact.createElement(ClockApp, {
time = currentTime,
}))

wait(1)
end
end)

local function stop()
running = false
root:unmount()
rootInstance.Parent = nil
end

return stop
end
33 changes: 33 additions & 0 deletions examples/event/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
return function()
local PlayerGui = game:GetService("Players").LocalPlayer.PlayerGui

local Roact = require(game.ReplicatedStorage.Roact)

local app = Roact.createElement("ScreenGui", nil, {
Button = Roact.createElement("TextButton", {
Size = UDim2.new(0.5, 0, 0.5, 0),
Position = UDim2.new(0.5, 0, 0.5, 0),
AnchorPoint = Vector2.new(0.5, 0.5),

-- Attach event listeners using `Roact.Event[eventName]`
-- Event listeners get `rbx` as their first parameter
-- followed by their normal event arguments.
[Roact.Event.Activated] = function(rbx)
print("The button was clicked!")
end,
}),
})

local rootInstance = Instance.new("Folder")
rootInstance.Parent = PlayerGui

local root = Roact.createBlockingRoot(rootInstance)
root:render(app)

local function stop()
root:unmount()
rootInstance.Parent = nil
end

return stop
end
27 changes: 27 additions & 0 deletions examples/hello-roact/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
return function()
local PlayerGui = game:GetService("Players").LocalPlayer.PlayerGui

local Roact = require(game.ReplicatedStorage.Roact)

local app = Roact.createElement("ScreenGui", nil, {
Main = Roact.createElement("TextLabel", {
Size = UDim2.new(0, 400, 0, 300),
Position = UDim2.new(0.5, 0, 0.5, 0),
AnchorPoint = Vector2.new(0.5, 0.5),
Text = "Hello, Roact!",
}),
})

local rootInstance = Instance.new("Folder")
rootInstance.Parent = PlayerGui

local root = Roact.createBlockingRoot(rootInstance)
root:render(app)

local function stop()
root:unmount()
rootInstance.Parent = nil
end

return stop
end
Loading

0 comments on commit 3249d0c

Please sign in to comment.