diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000..28a642d5
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,7 @@
+Closes (ISSUES HERE).
+
+*Put your pull request body here!*
+
+Checklist before submitting:
+* [ ] Added/updated relevant tests
+* [ ] Added/updated documentation
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 9c4b7caa..1e923871 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,19 @@
+sourcemap.json
rotriever.lock
Packages/*
!Packages/.robloxrc
!Packages/_Workspace/
Packages/_Workspace/*
!Packages/_Workspace/.robloxrc
+standalone/Packages
**/*.rbx[lm]*
.DS_Store
.vscode
+roblox.yml
+roblox.toml
**/luacov.*
.idea/
+site/
+
+default_modules
+modules_*
diff --git a/.styluaignore b/.styluaignore
new file mode 100644
index 00000000..2c8a1314
--- /dev/null
+++ b/.styluaignore
@@ -0,0 +1 @@
+*.snap.lua
\ No newline at end of file
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000..329d988c
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,132 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, caste, color, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the
+ overall community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or
+ advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email
+ address, without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+lua-foundation@roblox.com.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
+
+Community Impact Guidelines were inspired by
+[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
+
+For answers to common questions about this code of conduct, see the FAQ at
+[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
+at [https://www.contributor-covenant.org/translations][translations].
+
+[homepage]: https://www.contributor-covenant.org
+[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
+[Mozilla CoC]: https://github.com/mozilla/diversity
+[FAQ]: https://www.contributor-covenant.org/faq
+[translations]: https://www.contributor-covenant.org/translations
diff --git a/DEVIATIONS.md b/DEVIATIONS.md
index 6759603a..25a835bd 100644
--- a/DEVIATIONS.md
+++ b/DEVIATIONS.md
@@ -4,25 +4,26 @@ Upstream naming and logic has some deviations and incompatibilities with existin
#### Table of Contents
* [Naming](#naming)
- * [Component Lifecycle](#component-lifecycle) ✔️
- * [Reserved Prop Keys: "ref"](#reserved-prop-keys-ref)
- * [Reserved Prop Keys: "key"](#reserved-prop-keys-key)
+ * [Component Lifecycle](#component-lifecycle) c
+ * [Reserved Prop Keys: "ref"](#reserved-prop-keys-ref) ✔️
+ * [Reserved Prop Keys: "key"](#reserved-prop-keys-key) ✔️
* [Reserved Prop Keys: "children"](#reserved-prop-keys-children) ✔️
* [Behavior](#behavior)
- * [Old Context](#old-context-roact-only)
- * [Context.Consumer Interface](#contextconsumer-interface)
+ * [Old Context](#old-context-roact-only) ✔️
+ * [Context.Consumer Interface](#contextconsumer-interface) ✔️
* [createFragment](#createfragment) ✔️
* [Ref Forwarding](#ref-forwarding)
* [Stable Keys](#stable-keys) ✔️
- * [Use of setState](#use-of-setstate)
- * [Functional setState](#functional-setstate)
+ * [Use of setState](#use-of-setstate) ✔️
+ * [Functional setState](#functional-setstate) ✔️
* [Roact.Portal](#roactportal) ✔️
- * [State Initialization](#state-initialization)
+ * [State Initialization](#state-initialization) ✔️
+ * [Functional setState Signature](#functional-setstate-signature) ✔️
## Naming
### Component Lifecycle
-**Status:** ✔️ Resolved
+**Status:** ✔️ Resolved (backwards compatible with deprecation warnings)
Details
@@ -56,7 +57,7 @@ An implementation of tactic #2 above was merged in [#88](https://github.com/Robl
### Reserved Prop Keys: "ref"
-**Status:** 🔨 In Progress (updating consumers to comply)
+**Status:** ✔️ Resolved (consumers updated to comply)
Upstream React reserves the prop key "ref". In Roact the "ref" key is replaced by a Symbol exported as part of the API and applied as a prop with the key `[Roact.Ref]`. This means that there's no need to reserve a key, because the key is already unique and has a special meaning.
@@ -102,7 +103,7 @@ This would be blocked by refactors to remove any existing uses of the `ref` key,
This alignment effort should be considered in tandem with that of [ref forwarding logic](#ref-forwarding).
### Reserved Prop Keys: "key"
-**Status:** 🔨 In Progress (updating consumers to comply)
+**Status:** ✔️ Resolved (consumers updated to comply)
Upstream React reserves the prop key "key". In Roact, "key" has no special meaning.
@@ -123,7 +124,7 @@ However, we may instead consider providing a `Roact.Key` symbol key that can be
For any approach to stable key assignment, we should additionally support Roact's approach. This alignment effort should be considered in tandem with that of [stable keys](#stable-keys)
### Reserved Prop Keys: "children"
-**Status:** ✔️ Resolved
+**Status:** ✔️ Resolved (consumers updated to comply)
Upstream React [reserves the prop key "children"](https://reactjs.org/docs/glossary.html#propschildren). In Roact the "children" key is replaced by a Symbol exported as part of the API and applied as a prop with the key `[Roact.Children]`. This means that there's no need to reserve a key, because the key is already unique and has a special meaning.
@@ -160,7 +161,7 @@ The most straightforward approach would be to export `Roact.Children` with a val
### Old Context (Roact only)
-**Status:** 🔨 In Progress (updating consumers to comply)
+**Status:** ✔️ Resolved (consumers updated to comply)
In Roact, the "old" context behavior was a `_context` field defined on every class component instance. To provide context, a component would mutate its `_context` field in `init`:
```lua
@@ -200,17 +201,27 @@ We'll likely have to modernize all existing uses of `_context` to instead use th
This is likely the biggest refactor effort that the Lua Apps adoption is contingent on. It also incurs some knock-on efforts on projects that the App depends upon, like [roact-rodux](https://github.com/roblox/roact-rodux/issues/26), which has some work completed, [but with unaddressed backwards compatibility problems](https://github.com/Roblox/roact-rodux/pull/38#issuecomment-644902307).
### Context.Consumer Interface
-**Status:** ❔ Alignment Strategy TBD
+**Status:** ✔️ Resolved (backwards compatible with deprecation warnings)
+
+ Details
-Stub: context consumer api doesn't match that of Roact's createContext context consumer
+The context consumer api doesn't match that of Roact's createContext context consumer.
+* Roact's implementation accepts a single prop, which is a render functions `render(contextObject) -> ReactElement`
+* React's implementation accepts no props, and a single child, which is a `render` function with the same signature as above
-#### In Production Code
+#### Implemented Alignment Strategy
+We've provided support for both interfaces. Resolution and more info at https://github.com/Roblox/roact-alignment/pull/119
+
-#### Proposed Alignment Strategy
-We'll probably want to support both interfaces
+### validateProps
+**Status:** ✔️ Resolved (backwards compatible)
+
+#### Implemented Alignment
+Roact 17 supports both validateProps. `checkPropTypes` method was expanded to include logic for validateProps. For full details, see [#131](https://github.com/Roblox/roact-alignment/pull/131).
+
### createFragment
-**Status:** ✔️ Resolved
+**Status:** ✔️ Resolved (backwards compatible with deprecation warnings)
Details
@@ -262,9 +273,9 @@ This would be a simple compatibility layer that should require very little maint
#### Implemented Alignment
The `createFragment` function described above was added to React.lua in [#92](https://github.com/Roblox/roact-alignment/pull/92/files).
-
+
### Ref Forwarding
-**Status:** 🔨 In Progress (updating consumers to comply)
+**Status:** ✔️ Resolved (consumers updated to comply)
Ref forwarding is possible in React via the [`forwardRef` API](https://reactjs.org/docs/forwarding-refs.html).
@@ -361,7 +372,7 @@ function NumberList(props)
-- its sibling elements
ListLayout = Roact.createElement("UIListLayout", {SortOrder = Enum.SortOrder.LayoutOrder})
}
- for i, number in ipairs(numbers) do
+ for i, number in numbers do
-- Here, the key in the list (`i`) is the key associated with the
-- equivalently-numbered element. It can be any kind of value.
listItems[i] = Roact.createElement("TextLabel", {
@@ -385,8 +396,111 @@ In the event that both a table key and the `key` prop are provided to the same e
An implementation of this approach was merged in [#68](https://github.com/Roblox/roact-alignment/pull/68).
+### Child Array Default Keys
+**Status:** ✔️ Resolved (backwards compatible)
+
+Details
+In React 17, keys are not applied to children in an array. However, Legacy Roact automatically set a child's key in an array of children to that child's index in the array. Some downstream behavior relies on this legacy roact behavior, so Roact 17 automatically applies a child's index as its stable key if it is in an array and not passed a key prop.
+
+
+#### Example
+React:
+```jsx
+function ChildrenArrayComponent(props) {
+ // The children of div are not given keys. Order is preservered inherently in the DOM.
+ return (
+
+
+
+
+
+ )
+}
+```
+
+Roact 17:
+```lua
+function ChildrenArrayComponent(props)
+ return Roact.createElement("Frame", nil, {
+ -- These children receive keys 1, 2, and 3, respectively
+ Roact.createElement(Foo),
+ Roact.createElement(Foo),
+ Roact.createElement(Bar),
+ })
+end
+```
+
+Equivalent Roact 17 with Lua Table Keys:
+```lua
+function ChildrenArrayComponent(props)
+ return Roact.createElement("Frame", nil, {
+ -- These children receive keys 1, 2, and 3, respectively
+ [1] = Roact.createElement(Foo),
+ [2] = Roact.createElement(Foo),
+ [3] = Roact.createElement(Bar),
+ })
+end
+```
+
+Equivalent Roact 17 with prop keys:
+```lua
+function ChildrenArrayComponent(props)
+ return Roact.createElement("Frame", nil, {
+ -- These children receive keys 1, 2, and 3, respectively
+ Roact.createElement(Foo, { key = 1 }),
+ Roact.createElement(Foo, { key = 2 }),
+ Roact.createElement(Bar, { key = 3 }),
+ })
+end
+```
+
+#### Proposed Alignment Strategy
+This change is fully backwards compatible with Legacy Roact. However, users should be wary of elements potentially re-mounting on render when children are not given keys. This case can occur when a user switches from rendering multiple child elements in an array to rendering a single child element. Re-mounting is inefficient in comparison to updating an element on render. To avoid this case, a single child element should be given a key. The particular case is shown below:
+
+Re-mounting Case (inefficient):
+```lua
+local root = Roact.createLegacyRoot(container)
+
+-- Initial Render
+root.render(Roact.createElement(Foo, nil, {
+ Roact.createElement(Bar),
+ Roact.createElement(Bar),
+ Roact.createElement(FooBar),
+}))
+
+-- Re-Render
+-- Bar will be remounted here. Roact 17 cannot discern if this is
+-- the same Bar as above, so it remounts it.
+root.render(Roact.createElement(Foo, nil, Roact.createElement(Bar)))
+```
+
+Update Case (efficient):
+```lua
+local root = Roact.createLegacyRoot(container)
+
+-- Initial Render
+-- Keys are not necessary here, they are applied by default
+-- as { 1 = Bar, 2 = Bar, 3 = FooBar }
+root.render(Roact.createElement(Foo, nil, {
+ Roact.createElement(Bar),
+ Roact.createElement(Bar),
+ Roact.createElement(FooBar),
+}))
+
+-- Re-Render
+-- Bar will be updated here, as the key allows Roact 17 to match
+-- this Bar to the first Bar above. If we instead set key to 2, it
+-- would match the second Bar above
+root.render(Roact.createElement(Foo, nil,
+ Roact.createElement(Bar, { key = 1 })
+))
+```
+
+
### Use of setState
-**Status:** ❔ Alignment Strategy TBD
+**Status:** ✔️ Resolved (aligned to legacy Roact)
+
+ Details
In React, `setState` is not allowed inside a constructor. Instead, it is recommended to assign directly to `this.state` (more info in the [React documentation](https://reactjs.org/docs/react-component.html#constructor))
@@ -416,15 +530,16 @@ end
#### In Production Code
It's difficult to measure this without relying heavily on formatting, but it seems that ~90 component definitions in the Lua App repo, including dependencies, invoke `setState` inside of their `init` functions (equivalent to a class component constructor).
-#### Proposed Alignment Strategy
-We should continue to support calling `setState` in init, and ensure that its behavior is equivalent to assigning directly to state.
+#### Implemented Alignment Strategy
+We continue to support calling `setState` in init, and ensure that its behavior is equivalent to assigning directly to state.
-This will maximize compatibility with existing Roact code, and does not risk incurring significant tech debt, as we anticipate that class components will become less ubiquitous as hooks begin to see adoption.
+This maximizes compatibility with existing Roact code, and does not risk incurring significant tech debt, as we anticipate that class components will become less ubiquitous as hooks begin to see adoption.
-This deviation may be non-trivial to implement, and might have some subtle and dangerous corner cases. We should apply thorough test cases to confirm that the behavior is as expected, which may mean adapting some of current Roact's tests.
+Resolution and more info at https://github.com/Roblox/roact-alignment/pull/124
+
### Functional setState
-**Status:** ❔ Alignment Strategy TBD
+**Status:** ✔️ Resolved (aligned to legacy Roact)
In both React and Roact, `setState` can accept a function as its argument in place of a table (with async rendering, this is encouraged as the default choice). In React, however, the argument passed to `setState` is invoked via `payload.call(instance, prevState, nextProps)`. In other words, React calls the function in such a way that the `instance` is in scope as `this` in the body of the updater function.
@@ -473,8 +588,13 @@ Alternatively, we might consider:
* Align all existing usages, modifying them to accept `self` as their first argument. While this seems reasonable on the surface, there are serious caveats. Since lua and js have different mechanisms of defining and calling methods with `self`, the exact _behavior_ will be more similar to upstream, but the _API_ will deviate and need to be called out in documentation. We'd likely also want to add additional warnings to detect expected misuses.
* Perform some trickery with `setfenv` to allow the arguments to be in the same place, but `self` to be accessible. This is ugly, because it won't be understood by linting and I don't actually know how it will interact with shadowing/closures. As far as I'm concerned, this is a non-option, but it's worth calling out for thoroughness.
+#### Implemented Alignment Strategy
+We opted to align to legacy roact in our code to reduce impact on adoption. There remains no known use case for explicit injection of `self`, especially when closing over `self` is trivial.
+
+This strategy was implemented in https://github.com/Roblox/roact-alignment/pull/160
+
### Roact.Portal
-**Status:** ✔️ Resolved (backwards compatible)
+**Status:** ✔️ Resolved (backwards compatible with deprecation warnings)
Details
@@ -521,7 +641,7 @@ We should create a special component called `Portal`, expose it via our compatib
### State Initialization
-**Status:** ❔ Alignment Strategy TBD
+**Status:** Alignment Strategy TBD
In Roact, ["stateful" components](https://roblox.github.io/roact/guide/state-and-lifecycle/) (equivalent of React's "class" components) will automatically initialize their state value to an empty table if it is not assigned via `init` or `getDerivedStateFromProps`.
@@ -556,7 +676,7 @@ end
function ShowCount:render()
return Roact.createElement("TextLabel", {
-- `self.state` is an empty table, and `self.state.count` is nil.
- -- Text will be left as its default value for a TextLabel
+ -- Text will be left as its default value for a TextLabel
Text = self.state.count,
})
end
@@ -565,8 +685,42 @@ end
#### In Production Code
It's difficult to find where this is relied upon in production!
+#### Alignment Strategy
+Accessing uninitialized state may not be strictly wrong, but it is still a code smell. To encourage proper state initialization, we introduced a compoatibility layer: a singleton `UninitializedState` sentinel object that provides a useful error when accessed in dev mode, and should work just like an empty table in non-dev.
+
+The solution was implemented here: https://github.com/Roblox/roact-alignment/pull/155
+
+### Functional setState Signature
+**Status:** ✔️ Resolved (minor deviation from upstream)
+
+
+In both [React](https://reactjs.org/docs/react-component.html#setstate) and [Roact](https://roblox.github.io/roact/api-reference/#setstate), class components can call `setState` with a function instead of a partial state table. When state updates rely on previous state, this can make them more resilient to multiple queued updates. This approach is favored by upstream documentation for this reason, and encouraged in Roact's documentation as well (in anticipation of async rendering).
+
+In React, the updater function is called with `this` in scope, using JavaScript's `bind` function. This poses a problem for the translation: if we want to align this behavior, we need to change the signature of the state updater from `(state, props) -> partialState` to `(self, state, props) -> partialState`. This would be a backwards-incompatible change
+
+#### Example
+React (adapted from the React documentation):
+```js
+this.setState((state, props) => {
+ // `this` is in scope here and can be read from
+ return {counter: state.counter + props.step};
+});
+```
+
+Roact:
+```lua
+self:setState(function(state, props)
+ -- `self` is NOT in scope here
+ return { counter = state.counter + props.step }
+end);
+```
+
+#### In Production Code
+This functionality is used relatively sparingly in the lua-apps code base (including dependencies), only ~15 confirmed usages and ~45 more possible ones (more investigation needed to confirm).
+
#### Proposed Alignment Strategy
-We should adopt Roact's behavior to provide less likelihood of runtime surprises.
+While it's possible to adapt to the upstream behavior, I was not able to find _any_ idiomatic usages of `this` in the body of a `setState` updater function. We should keep Roact's current behavior, which encourages function purity and serves all known use cases without issue.
+
# Unique Features
Roact has a couple of unique features that are not present in upstream, while a number of new features from upstream will be introduced by the alignment effort.
@@ -585,22 +739,32 @@ Upstream React introduces a number of incoming features. Some of these are alrea
_(section needs filling in...)_
### Hooks
-...
+[React Hooks](https://reactjs.org/docs/hooks-intro.html)
+
+Most hooks are ported and exposed (a few experimental and in-development ones are not yet available): https://github.com/Roblox/roact-alignment/blob/v17.0.1-preview.0/modules/react/src/React.lua#L35-L45
### Memo
-...
+[React Memo](https://reactjs.org/docs/react-api.html#reactmemo)
+
+Memo is ported and exposed via `React.memo`.
### Lazy
-...
+[React Lazy](https://reactjs.org/docs/code-splitting.html#reactlazy)
+
+Lazy is ported and exposed via `React.lazy`.
### Suspense
-...
+[React Suspense](https://reactjs.org/docs/react-api.html#reactsuspense)
+
+Suspense is ported and exposed via `React.Suspense`.
### Error Boundaries
-...
+[React Error Boundaries](https://reactjs.org/docs/error-boundaries.html)
-### DEV mode
-...
+Error boundaries are ported and exposed via the component lifecycle methods `getDerivedStateFromError` and `componentDidCatch`.
-### Better Error Messages
-...
+### DEV mode
+DEV Mode can be enabled by setting the `__DEV__` global to `true` before the initial require of any Roact package. You can accomplish this either by:
+* In Roblox Studio, executing `_G.__DEV__ = true` at the entry point of your test or application (before requiring any React packages)
+* In roblox-cli, including the argument `--lua.globals=__DEV__=true` when using the `run` command
+* If `__COMPAT_WARNINGS__` is set, it will output Legacy Roact compatibility layer warnings, which will help teams write code that will be forward compatible at the next major version update to Roact.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..eab237bd
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020-2023 Roblox Corporation
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index 68cd9583..d1ae0a5a 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,19 @@
+# Roact Alignment
[](https://github.com/Roblox/roact-alignment/actions?query=workflow%3ACI+branch%3Amaster)
[](https://coveralls.io/github/Roblox/roact-alignment?branch=master)
+[](https://roblox.github.io/roact-alignment/)
-# Roact Alignment
-A temporary ground-up Roact repository that will track our preliminary alignment with React, starting with leaf nodes like the scheduler.
+A comprehensive, but not exhaustive, translation of upstream ReactJS 17.x into Roblox Lua.
+
+## TL;DR
+
+* In a new project, you can consume this library by adding this line to your rotriever.toml
+ * `React = "github.com/roblox/roact-alignment@17.0.1"`
+* If you have legacy Roact code, use the roact-compat library instead
+ * `RoactCompat = "github.com/roblox/roact-alignment@17.0.1"`
+* Make sure you are using the latest [rotriever](https://github.com/Roblox/rotriever/releases) 0.5 (or later) release
+ * you can download the release binary, or add it to your `foreman.toml`: ```rotrieve = { source = "roblox/rotriever", version = "=0.5.12" }```
+* For unit testing components and trees of components, you'll want to use the [`act()`](https://github.com/threepointone/react-act-examples/blob/master/sync.md) API exported from the react-roblox package.
## Status
The [react repo](https://github.com/facebook/react) is a monorepo with a number of member projects in its `packages` folder, managed by a yarn workspace. Below is a description of each of those package, its status in our alignment repo, and how it likely fits into our future plans.
@@ -11,54 +22,54 @@ The [react repo](https://github.com/facebook/react) is a monorepo with a number
| Project | Description | Status | Plan | Notes |
| - | - | - | - | - |
-| `create-subscription` | Used for subscribing to external data | ❌ Not ported | ❔ Not yet assessed | |
-| `dom-event-testing-library` | Dom event simulation for tests | ❌ Not ported | ❔ Not yet assessed | May inspire Rhodium improvements |
-| `eslint-plugin-react-hooks` | Linting plugin for hooks rules | ❌ Not ported | ❔ Not yet assessed | Depends on future linting tools |
-| `jest-mock-scheduler` | Reexports scheduler testing utilities | ❌ Not ported | ❔ Not yet assessed | |
-| 📌`jest-react` | Jest matchers and utilities | ✔️ Ported | | Haven't yet run into any uses of this in tests we've ported so far |
-| 📌`react` | Base react interface | 🔨 Port in progress | | Defines basic shape of internals like Components and Elements. We may add things like Bindings here. |
-| `react-art` | For drawing vector graphics | ❌ Not ported | ➖ Unlikely to be ported | |
-| `react-cache` | Basic cache for use with experimental React features | ❌ Not ported | ❔ Not yet assessed | API is flagged as unstable |
-| `react-client` | Experimental package for consuming React streaming models | ❌ Not ported | ❔ Not yet assessed | API considered unstable. Might be worth investigating if it stabilizes |
-| `react-debug-tools` | Experimental debugger package | ❌ Not ported | ❔ Not yet assessed | API considered unstable |
+| `create-subscription` | Used for subscribing to external data | ❌ Not ported | ➖ Unlikely to be ported | Intended to help transition from pre-React 17 |
+| `dom-event-testing-library` | Dom event simulation for tests | ❌ Not ported | ➖ Unlikely to be ported | Obviated by [ReactTestingLibrary](https://github.com/roblox/dom-testing-library-lua) |
+| `eslint-plugin-react-hooks` | Linting plugin for hooks rules | ❌ Not ported | 🔁 Revisit if needed | Should be a reference for future linting tools |
+| `jest-mock-scheduler` | Reexports scheduler testing utilities | ❌ Not ported | ➖ Unlikely to be ported | Exports internal utilities for mocking scheduler, not as useful as `act()` |
+| 📌`jest-react` | Jest matchers and utilities | ✔️ Ported | | Used for internal framework tests |
+| 📌`react` | Base react interface | ✔️ Ported | | Defines basic shape of internals like Components and Elements. We added added Roblox-specifics like Bindings, but otherwise comply with upstream ReactJS. |
+| `react-art` | For drawing vector graphics | ❌ Not ported | ➖ Unlikely to be ported | No vector graphics on Roblox |
+| `react-cache` | Basic cache for use with experimental React features | ✔️ Ported | | API is flagged as unstable, is stable in React 18, used in advanced Suspense cases |
+| `react-client` | Experimental package for consuming React streaming models | ❌ Not ported | 🔁 Revisit if needed | API considered unstable. Might be worth investigating if it stabilizes |
+| `react-debug-tools` | Experimental debugger package | ✔️ Ported | | Used by DevTools and Roblox Studio Inspector |
| `react-devtools` | Top-level app for react devtools | ❌ Not ported | ➕ Likely to be ported | Devtools needs to be addressed as a whole to see where/how it translates |
| `react-devtools-core` | Standalone devtools impl | ❌ Not ported | ➕ Likely to be ported | Devtools needs to be addressed as a whole to see where/how it translates |
-| `react-devtools-extensions` | Devtools browser extension | ❌ Not ported | ➖ Unlikely to be ported | |
+| `react-devtools-extensions` | Devtools browser extension | ❌ Not ported | ➖ Unlikely to be ported | Devtools needs to be addressed as a whole to see where/how it translates |
| `react-devtools-inline` | Impl for embedding in browser-based IDEs | ❌ Not ported | ➕ Likely to be ported | Devtools needs to be addressed as a whole to see where/how it translates |
-| `react-devtools-scheduling-profiler` | Experimental concurrent mode profiler | ❌ Not ported | ❔ Not yet assessed | |
-| `react-devtools-shared` | Private shared utilities for devtools | ❌ Not ported | ➕ Likely to be ported | Devtools needs to be addressed as a whole to see where/how it translates |
-| `react-devtools-shell` | Harness for testing other devtools packages | ❌ Not ported | ❔ Not yet assessed | Devtools needs to be addressed as a whole to see where/how it translates |
-| `react-dom` | Entrypoint for DOM and server renderers | ❌ Not ported | ➖ Unlikely to be ported | Will inform top-level interface, but will be mostly replaced with Roblox-specific logic |
-| `react-fetch` | For use with experimental React features | ❌ Not ported | ❔ Not yet assessed | API considered unstable |
-| `react-interactions` | For use with experimental React features | ❌ Not ported | ❔ Not yet assessed | |
+| `react-devtools-scheduling-profiler` | Experimental concurrent mode profiler | ❌ Not ported | 🔁 Revisit if needed | Supplanted in React 18 by `react-devtools-timeline` package |
+| `react-devtools-shared` | Private shared utilities for devtools | ✔️ Ported | | Used by Roblox Studio Inspector |
+| `react-devtools-shell` | Harness for testing other devtools packages | ❌ Not ported | ➖ Unlikely to be ported | Devtools needs to be addressed as a whole to see where/how it translates |
+| `react-dom` | Entrypoint for DOM and server renderers | ❌ Not ported | ➖ Unlikely to be ported | Not ported directly, but it heavily inspired the React-Roblox renderer interface and implementation |
+| `react-fetch` | For use with experimental React features | ❌ Not ported | ➖ Unlikely to be ported | API considered unstable, [removed in React 18](https://github.com/facebook/react/pull/25577) |
+| `react-interactions` | For use with experimental React features | ❌ Not ported | ➖ Unlikely to be ported | API unstable, used only with internal features |
| 📌`react-is` | Runtime type checks for React elements | ✔️ Ported | | |
-| `react-native-renderer` | Renderer interface for react-native | ❌ Not ported | ❔ Not yet assessed | This package has no readme, so it's hard to understand its scope |
-| 📌`react-noop-renderer` | Renderer used for debugging Fiber | ✔️ Ported | | Will be needed to verify our Fiber/Reconciler work |
+| `react-native-renderer` | Renderer interface for react-native | ❌ Not ported | ➖ Unlikely to be ported | Not well documented, likely does not apply to Roblox |
+| 📌`react-noop-renderer` | Renderer used for debugging Fiber | ✔️ Ported | | Used heavily for internal framework testing |
| 📌`react-reconciler` | Reconciler implementation used with various renderers | ✔️ Ported | | Bulk of React's complicated logic lives here |
-| `react-refresh` | Wiring for Fast Refresh | ❌ Not ported | ❔ Not yet assessed, depend on applicability | Officially supported successor to "hot reloading" |
-| `react-server` | Experimental package for creating React streaming server renderers | ❌ Not ported | ❔ Not yet assessed | |
-| `react-test-renderer` | Test renderer with dom snapshotting | ✔️ Ported | | Used for testing much of React's internals |
-| `react-transport-dom-delay` | Internal package, likely for testing | ❌ Not ported | ➖ Unlikely to be ported | No readme in package |
-| `react-transport-dom-webpack` | Related to above | ❌ Not ported | ➖ Unlikely to be ported | Appears to be webpack-specific |
-| 📌`scheduler` | Cooperative scheduling implementation | ✔️ Ported | | Tracing feature is excluded, will be needed at some point for devtools |
-| 📌`shared` | Loose collection of shared utilities and definitions | ✔️ Ported | | Working with upstream to see if this can be cleaned up |
-| `use-subscription` | Hook for managing subscriptions in concurrent mode | ❌ Not ported | ❔ Not yet assessed | Not sure if/how this will apply to Roblox |
-
-Projects not in the react repo:
+| `react-refresh` | Wiring for Fast Refresh | ✔️ Ported | | Successor to "hot reloading", but relies on bundler step |
+| `react-server` | Experimental package for creating React streaming server renderers | ❌ Not ported | 🔁 Revisit if needed | API considered unstable. Might be worth investigating if it stabilizes |
+| `react-test-renderer` | Test renderer helpful utilities and snapshot support | ✔️ Ported | | Used for testing much of React's internals, can be used by client developers |
+| `react-transport-dom-delay` | Internal package, likely for testing | ❌ Not ported | ➖ Unlikely to be ported | Internal library for experimental React Flight feature |
+| `react-transport-dom-webpack` | Related to above | ❌ Not ported | ➖ Unlikely to be ported | Webpack-specific bindings for experimental React Flight feature |
+| 📌`scheduler` | Cooperative scheduling implementation | ✔️ Ported | | Includes Tracing and Profiling features, which are enabled through ReactFeatureFlags |
+| 📌`shared` | Loose collection of shared utilities and definitions | ✔️ Ported | | We pushed many things into this leaf node module to fix circular dependencies. Working with upstream to clean this up. |
+| `use-subscription` | Hook for managing subscriptions in concurrent mode | ❌ Not ported | 🔁 Revisit if needed | Supplanted by [use-sync-external-store](https://github.com/facebook/react/tree/main/packages/use-sync-external-store) in upstream |
+
+Projects not in the upstream React repo:
| Project | Description | Notes |
| - | - | - |
| 📌`react-shallow-renderer` | Shallow renderer used in tests for some older React features. Re-exported alongside `react-test-renderer`, source of truth [here](https://github.com/NMinhNguyen/react-shallow-renderer). | ✔️ Ported - with tests that are helping us exercise functionality in the `react` package |
-| `roblox-jest` | Custom matchers and timer logic for TestEZ | A rough approximation of what we'll eventually have with the [`jest` alignment effort](https://github.com/Roblox/lest-alignment) |
-| `roblox-js-polyfill` | Implementations of JS specific interfaces or functionality | Most implementations are incomplete or slightly adjusted for Lua |
+| `react-roblox` | Based on react-dom renderer, shares much of its code and public interface.| Also exports [`act()`](https://github.com/threepointone/react-act-examples/blob/master/sync.md) functionality, which is *required* for testing components that are asynchronously rendered (the default). |
+| `roact-compat` | A comaptibility layer that emulates some deprecated behaviors of legacy Roact | Meant to ease initial adoption of Roact 17, using React APIs directly is encouraged and necessary for newer functonality (eg Hooks) |
## Deviations from [Roact](https://github.com/roblox/roact)
This repo is meant to supplant the `Roact` project, which is an open-source project that currently powers the majority of the Lua App and is used by the community as well. Our goal is to be as compatible as possible with Roact by the time we're ready to start adopting this alignment effort for use.
With that in mind, however, there will still be a small number of behavioral deviations that make the transition from existing Roact smoother, or account for nuances of the Roblox ecosystem:
* Stable Keys: Aligned Roact will allow table keys to be used as stable keys for child elements, equivalent to the behavior relied upon in Roact today
-* Context: Current Roact's deprecated `_context` feature will not be present in aligned Roact; users will have to switch to the `createContext` feature, which is present in both current and aligned Roact and is semantically equivalent
+* Context: Legacy Roact's deprecated `_context` feature will not be present in aligned Roact; users will have to switch to the `createContext` feature, which is present in both current and aligned Roact and is semantically equivalent
* Class Component Refs: Aligned Roact will allow refs provided to class components (referred to in Roact documentation as "stateful components") to point to the actual component instance. This is not supported in current Roact, and there may be changes around the `Roact.Ref` prop key to support this with minimal disruption
-* Bindings: We intend to keep `createBindings` and `joinBindings`, a feature unique to Roact and [documented here](https://roblox.github.io/roact/api-reference#roactcreatebinding)
+* Bindings: We intend to keep `createBinding` and `joinBindings`, a feature unique to Roact and [documented here](https://roblox.github.io/roact/api-reference#roactcreatebinding)
See [this document](DEVIATIONS.md) for details about any deviations and the design and refactoring efforts being proposed to address them.
@@ -68,7 +79,7 @@ You need to create a GitHub Access Token:
* GitHub.com -> Settings -> Developer Settings -> Personal Access Tokens
* Your token must have the `repo` and read:packages` scopes
* On that same page, you then need to click Enable SSO
-* BE SURE TO COPY THE ACCESS TOKEN SOMEWHERE
+* BE SURE TO COPY THE ACCESS TOKEN SOMEWHERE
```
npm login --registry=https://npm.pkg.github.com/ --scope=@roblox
@@ -98,33 +109,35 @@ cd roact-alignment
roblox-cli analyze default.project.json
```
-Foreman is an un-package manager that retrieves code directly from GitHub repositories. We'll use this to get a Lua package manager and other utilities. The Foreman packages are listed in `foreman.toml`. Foreman uses Rust, so you'll have to install Rust first.
+Foreman is an un-package manager that retrieves code directly from GitHub repositories. We'll use this to get a Lua package manager and other utilities. The Foreman packages are listed in `foreman.toml`.
+
+You can install Foreman from a binary by downloading the appropriate version for your platform from [the GitHub releases page](https://github.com/roblox/foreman/releases/latest).
+
+Alternatively, since Foreman uses Rust, you can install Rust and use cargo to install it:
```
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
export PATH=$PATH:$HOME/.cargo/bin
cargo install foreman
-foreman github-auth
-foreman install
-export PATH=$PATH:~/.foreman/bin/ # you might want to add this to your bash profile file too
```
-Now you can run the tests, edit code, and contribute! Next we need to install our Lua package dependencies. We do this with a tool called Rotriever, which Foreman just installed for us. The package dependencies are listed in `rotriever.toml`.
-
+Once Foreman is installed, provide an API token so that it can access private tools like [rotriever](https://github.com/roblox/rotriever) and make sure that it's added to your path.
```
-rotrieve install
+foreman github-auth
+foreman install
+export PATH=~/.foreman/bin/:$PATH # you might want to add this to your bash profile file too
```
-Next we're going to use Rojo (installed by Foreman above) to compile and package our Lua code into a format that Roblox understands.
+Now you can run the tests, edit code, and contribute! Next we need to install our Lua package dependencies. We do this with a tool called Rotriever, which Foreman just installed for us. The package dependencies are listed in `rotriever.toml`.
```
-rojo build tests.project.json --output model.rbxmx
+rotrieve install
```
-Now we can use `roblox-cli` to run our tests. We specify the Rojo build output file and our test runner file.
+Now we can use `roblox-cli` to run our tests. We need to specify some additional arguments to make sure that the latest luau language features are enabled as well as `debug.loadmodule`, which allows us to reset module state between tests.
```
-roblox-cli run --load.model model.rbxmx --run bin/spec.lua
+roblox-cli run --load.model tests.project.json --run bin/spec.lua --fastFlags.overrides EnableLoadModule=true --fastFlags.allOnLuau
```
### Common Issues
@@ -138,7 +151,7 @@ To avoid this in the future, be sure that your foreman binary path is *before* t
## Contribution Guidelines
-* Try to keep the directory structure, file name/location, and code symbol names aligned with React upstream. At the top of the mirrored files, put a comment in this format that includes the specific hash of the version of the file you're mirroring:
+* Try to keep the directory structure, file name/location, and code symbol names aligned with React upstream. At the top of the mirrored files, put a comment in this format that includes the specific hash of the version of the file you're mirroring:
```
-- ROBLOX upstream https://github.com/facebook/react/blob/9abc2785cb070148d64fae81e523246b90b92016/packages/scheduler/src/Scheduler.js
```
@@ -147,9 +160,9 @@ To avoid this in the future, be sure that your foreman binary path is *before* t
* If you have a deviation from upstream code logic for Lua-specific reasons (1-based array indices, etc) put a comment above the deviated line:
```
-- ROBLOX deviation: use explicit nil check instead of falsey
-```
+```
-* For deviations due to Lua language differences (no spread operator) that don't involve changing the logic, don't put a deviation comment. Just use the appropriate equivalent from the Cryo and other utility libraries.
+* For deviations due to Lua language differences (no spread operator) that don't involve changing the logic, don't put a deviation comment. Just use the appropriate equivalent from the es7-luau (fka LuauPolyfill) and other utility libraries.
* For files that are new and Roblox-specific, use the file name: ```Timeout.roblox.lua```
@@ -159,6 +172,8 @@ To avoid this in the future, be sure that your foreman binary path is *before* t
First, install the roblox-lrdb debugger extension for VSCode (following [the installation instructions here](https://github.com/Roblox/vscode-rbx-lrdb)).
+Note: As specified in the lrdb installation instructions, you _must_ be connected to the VPN in order for the Internal Tool Provider to be able to download what it needs.
+
In order for breakpoints to work correctly, you'll need to disable the module reloading behavior that relies on `debug.loadmodule` by providing the `__NO_LOADMODULE__` global. To do so, create a `launch.json` file with the following contents:
```
{
@@ -226,4 +241,3 @@ Shortly after you see the status bar change colors to indicate an active debugge

Note that VS Code even tells you the value of the variables on the line. Pretty cool!
-
diff --git a/WorkspaceStatic/jest.config.lua b/WorkspaceStatic/jest.config.lua
new file mode 100644
index 00000000..2ca8c9d4
--- /dev/null
+++ b/WorkspaceStatic/jest.config.lua
@@ -0,0 +1,24 @@
+local Workspace = script.Parent
+
+-- In case we need to specify a custom testSetupFile for a project, we need to do that in in a separate jest.config.lua file that's in the project's root folder.
+-- Therefore we specify the project here and provide it to the "projects" field in this config file.
+-- We also need to add the project to the "testPathIgnorePatterns" field so that Jest doesn't try to run the project's tests again.
+local projectsWithCustomJestConfig = {
+ Workspace.ReactDevtoolsShared.ReactDevtoolsShared,
+}
+local testPathIgnorePatterns = {}
+local allProjects = { Workspace }
+
+for _, project in projectsWithCustomJestConfig do
+ table.insert(testPathIgnorePatterns, tostring(project))
+ table.insert(allProjects, project)
+end
+
+return {
+ setupFilesAfterEnv = { Workspace.jest.testSetupFile },
+ projects = allProjects,
+ testMatch = {
+ "**/__tests__/*.(spec|test)",
+ },
+ testPathIgnorePatterns = testPathIgnorePatterns,
+}
diff --git a/WorkspaceStatic/jest/.robloxrc b/WorkspaceStatic/jest/.robloxrc
new file mode 100644
index 00000000..8f779576
--- /dev/null
+++ b/WorkspaceStatic/jest/.robloxrc
@@ -0,0 +1,8 @@
+{
+ "language": {
+ "mode": "strict"
+ },
+ "lint": {
+ "*": "enabled"
+ }
+}
\ No newline at end of file
diff --git a/WorkspaceStatic/jest/matchers/__tests__/toWarnDev.spec.lua b/WorkspaceStatic/jest/matchers/__tests__/toWarnDev.spec.lua
new file mode 100644
index 00000000..a53edd79
--- /dev/null
+++ b/WorkspaceStatic/jest/matchers/__tests__/toWarnDev.spec.lua
@@ -0,0 +1,551 @@
+-- ROBLOX upstream https://github.com/facebook/react/blob/0b5a26a4895261894f04e50d5a700e83b9c0dcf6/scripts/jest/matchers/__tests__/toWarnDev-test.js
+--[[*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @emails react-core
+ ]]
+local Packages = script.Parent.Parent.Parent.Parent.TestRunner
+local ReactGlobals = require(Packages.Dev.ReactGlobals)
+local JestGlobals = require(Packages.Dev.JestGlobals)
+local jestExpect = JestGlobals.expect
+local beforeEach = JestGlobals.beforeEach
+local beforeAll = JestGlobals.beforeAll
+local describe = JestGlobals.describe
+local it = JestGlobals.it
+local toErrorDev = require(script.Parent.Parent.Parent.matchers.toErrorDev)
+local toWarnDev = require(script.Parent.Parent.Parent.matchers.toWarnDev)
+
+local console
+
+beforeAll(function()
+ jestExpect.extend({
+ toErrorDev = toErrorDev,
+ toWarnDev = toWarnDev,
+ })
+end)
+
+beforeEach(function()
+ local LuauPolyfill = require(Packages.Dev.LuauPolyfill)
+ console = LuauPolyfill.console
+end)
+
+describe("toErrorDev", function()
+ it("does not fail if a warning contains a stack", function()
+ jestExpect(function()
+ if ReactGlobals.__DEV__ then
+ console.error("Hello\n in div")
+ end
+ end).toErrorDev("Hello")
+ end)
+
+ it("does not fail if all warnings contain a stack", function()
+ jestExpect(function()
+ if ReactGlobals.__DEV__ then
+ console.error("Hello\n in div")
+ console.error("Good day\n in div")
+ console.error("Bye\n in div")
+ end
+ end).toErrorDev({
+ "Hello",
+ "Good day",
+ "Bye",
+ })
+ end)
+
+ it("does not fail if warnings without stack explicitly opt out", function()
+ jestExpect(function()
+ if ReactGlobals.__DEV__ then
+ console.error("Hello")
+ end
+ end).toErrorDev("Hello", { withoutStack = true })
+ jestExpect(function()
+ if ReactGlobals.__DEV__ then
+ console.error("Hello")
+ console.error("Good day")
+ console.error("Bye")
+ end
+ end).toErrorDev({
+ "Hello",
+ "Good day",
+ "Bye",
+ }, {
+ withoutStack = true,
+ })
+ end)
+
+ it(
+ "does not fail when expected stack-less warning number matches the actual one",
+ function()
+ jestExpect(function()
+ if ReactGlobals.__DEV__ then
+ console.error("Hello\n in div")
+ console.error("Good day")
+ console.error("Bye\n in div")
+ end
+ end).toErrorDev({
+ "Hello",
+ "Good day",
+ "Bye",
+ }, {
+ withoutStack = 1,
+ })
+ end
+ )
+
+ if ReactGlobals.__DEV__ then
+ -- // Helper methods avoids invalid toWarn().toThrow() nesting
+ -- // See no-to-warn-dev-within-to-throw
+ local function expectToWarnAndToThrow(expectBlock, expectedErrorMessage)
+ -- deviation: use TestEZ to thow to get a better error message
+ jestExpect(expectBlock).toThrow(expectedErrorMessage)
+ end
+
+ it("fails if a warning does not contain a stack", function()
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.error("Hello")
+ end).toErrorDev("Hello")
+ end, "Received warning unexpectedly does not include a component stack")
+ end)
+
+ it("fails if some warnings do not contain a stack", function()
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.error("Hello\n in div")
+ console.error("Good day\n in div")
+ console.error("Bye")
+ end).toErrorDev({
+ "Hello",
+ "Good day",
+ "Bye",
+ })
+ end, "Received warning unexpectedly does not include a component stack")
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.error("Hello")
+ console.error("Good day\n in div")
+ console.error("Bye\n in div")
+ end).toErrorDev({
+ "Hello",
+ "Good day",
+ "Bye",
+ })
+ end, "Received warning unexpectedly does not include a component stack")
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.error("Hello\n in div")
+ console.error("Good day")
+ console.error("Bye\n in div")
+ end).toErrorDev({
+ "Hello",
+ "Good day",
+ "Bye",
+ })
+ end, "Received warning unexpectedly does not include a component stack")
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.error("Hello")
+ console.error("Good day")
+ console.error("Bye")
+ end).toErrorDev({
+ "Hello",
+ "Good day",
+ "Bye",
+ })
+ end, "Received warning unexpectedly does not include a component stack")
+ end)
+
+ it("fails if warning is expected to not have a stack, but does", function()
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.error("Hello\n in div")
+ end).toErrorDev("Hello", { withoutStack = true })
+ end, "Received warning unexpectedly includes a component stack")
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.error("Hello\n in div")
+ console.error("Good day")
+ console.error("Bye\n in div")
+ end).toErrorDev({
+ "Hello",
+ "Good day",
+ "Bye",
+ }, {
+ withoutStack = true,
+ })
+ end, "Received warning unexpectedly includes a component stack")
+ end)
+
+ it(
+ "fails if expected stack-less warning number does not match the actual one",
+ function()
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.error("Hello\n in div")
+ console.error("Good day")
+ console.error("Bye\n in div")
+ end).toErrorDev({
+ "Hello",
+ "Good day",
+ "Bye",
+ }, {
+ withoutStack = 4,
+ })
+ end, "Expected 4 warnings without a component stack but received 1")
+ end
+ )
+
+ it("fails if withoutStack is invalid", function()
+ -- deviation: null should error, but not undefined. Since they're the same
+ -- in Lua, we can comment the expectation
+ -- expectToWarnAndToThrow(function()
+ -- jestExpect(function()
+ -- console.error("Hi")
+ -- end).toErrorDev("Hi", {withoutStack = null})
+ -- end, "Instead received object")
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.error("Hi")
+ end).toErrorDev("Hi", { withoutStack = {} })
+ end, "Instead received table")
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.error("Hi")
+ end).toErrorDev("Hi", {
+ withoutStack = "haha",
+ })
+ end, "Instead received string")
+ end)
+
+ it("fails if the argument number does not match", function()
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.error("Hi %s", "Sara", "extra")
+ end).toErrorDev("Hi", { withoutStack = true })
+ end, "Received 2 arguments for a message with 1 placeholders")
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.error("Hi %s")
+ end).toErrorDev("Hi", { withoutStack = true })
+ end, "Received 0 arguments for a message with 1 placeholders")
+ end)
+
+ it("fails if stack is passed twice", function()
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.error("Hi %s%s", "\n in div", "\n in div")
+ end).toErrorDev("Hi")
+ end, "Received more than one component stack for a warning")
+ end)
+
+ it("fails if multiple strings are passed without an array wrapper", function()
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.error("Hi \n in div")
+ end).toErrorDev("Hi", "Bye")
+ end, "toErrorDev() second argument, when present, should be an object")
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.error("Hi \n in div")
+ console.error("Bye \n in div")
+ end).toErrorDev("Hi", "Bye")
+ end, "toErrorDev() second argument, when present, should be an object")
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.error("Hi \n in div")
+ console.error("Wow \n in div")
+ console.error("Bye \n in div")
+ end).toErrorDev("Hi", "Bye")
+ end, "toErrorDev() second argument, when present, should be an object")
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.error("Hi \n in div")
+ console.error("Wow \n in div")
+ console.error("Bye \n in div")
+ end).toErrorDev("Hi", "Wow", "Bye")
+ end, "toErrorDev() second argument, when present, should be an object")
+ end)
+
+ it("fails on more than two arguments", function()
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.error("Hi \n in div")
+ console.error("Wow \n in div")
+ console.error("Bye \n in div")
+ end).toErrorDev("Hi", nil, "Bye")
+ end, "toErrorDev() received more than two arguments.")
+ end)
+ end
+end)
+
+describe("toWarnDev", function()
+ it("does not fail if a warning contains a stack", function()
+ jestExpect(function()
+ if ReactGlobals.__DEV__ then
+ console.warn("Hello\n in div")
+ end
+ end).toWarnDev("Hello")
+ end)
+
+ it("does not fail if all warnings contain a stack", function()
+ jestExpect(function()
+ if ReactGlobals.__DEV__ then
+ console.warn("Hello\n in div")
+ console.warn("Good day\n in div")
+ console.warn("Bye\n in div")
+ end
+ end).toWarnDev({
+ "Hello",
+ "Good day",
+ "Bye",
+ })
+ end)
+
+ it("does not fail if warnings without stack explicitly opt out", function()
+ jestExpect(function()
+ if ReactGlobals.__DEV__ then
+ console.warn("Hello")
+ end
+ end).toWarnDev("Hello", { withoutStack = true })
+ jestExpect(function()
+ if ReactGlobals.__DEV__ then
+ console.warn("Hello")
+ console.warn("Good day")
+ console.warn("Bye")
+ end
+ end).toWarnDev({
+ "Hello",
+ "Good day",
+ "Bye",
+ }, {
+ withoutStack = true,
+ })
+ end)
+
+ it(
+ "does not fail when expected stack-less warning number matches the actual one",
+ function()
+ jestExpect(function()
+ if ReactGlobals.__DEV__ then
+ console.warn("Hello\n in div")
+ console.warn("Good day")
+ console.warn("Bye\n in div")
+ end
+ end).toWarnDev({
+ "Hello",
+ "Good day",
+ "Bye",
+ }, {
+ withoutStack = 1,
+ })
+ end
+ )
+
+ if ReactGlobals.__DEV__ then
+ -- // Helper methods avoids invalid toWarn().toThrow() nesting
+ -- // See no-to-warn-dev-within-to-throw
+ local function expectToWarnAndToThrow(expectBlock, expectedErrorMessage)
+ -- deviation: use TestEZ to thow to get a better error message
+ jestExpect(expectBlock).toThrow(expectedErrorMessage)
+ end
+
+ it("fails if a warning does not contain a stack", function()
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.warn("Hello")
+ end).toWarnDev("Hello")
+ end, "Received warning unexpectedly does not include a component stack")
+ end)
+
+ it("fails if some warnings do not contain a stack", function()
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.warn("Hello\n in div")
+ console.warn("Good day\n in div")
+ console.warn("Bye")
+ end).toWarnDev({
+ "Hello",
+ "Good day",
+ "Bye",
+ })
+ end, "Received warning unexpectedly does not include a component stack")
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.warn("Hello")
+ console.warn("Good day\n in div")
+ console.warn("Bye\n in div")
+ end).toWarnDev({
+ "Hello",
+ "Good day",
+ "Bye",
+ })
+ end, "Received warning unexpectedly does not include a component stack")
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.warn("Hello\n in div")
+ console.warn("Good day")
+ console.warn("Bye\n in div")
+ end).toWarnDev({
+ "Hello",
+ "Good day",
+ "Bye",
+ })
+ end, "Received warning unexpectedly does not include a component stack")
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.warn("Hello")
+ console.warn("Good day")
+ console.warn("Bye")
+ end).toWarnDev({
+ "Hello",
+ "Good day",
+ "Bye",
+ })
+ end, "Received warning unexpectedly does not include a component stack")
+ end)
+
+ it("fails if warning is expected to not have a stack, but does", function()
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.warn("Hello\n in div")
+ end).toWarnDev("Hello", { withoutStack = true })
+ end, "Received warning unexpectedly includes a component stack")
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.warn("Hello\n in div")
+ console.warn("Good day")
+ console.warn("Bye\n in div")
+ end).toWarnDev({
+ "Hello",
+ "Good day",
+ "Bye",
+ }, {
+ withoutStack = true,
+ })
+ end, "Received warning unexpectedly includes a component stack")
+ end)
+
+ it(
+ "fails if expected stack-less warning number does not match the actual one",
+ function()
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.warn("Hello\n in div")
+ console.warn("Good day")
+ console.warn("Bye\n in div")
+ end).toWarnDev({
+ "Hello",
+ "Good day",
+ "Bye",
+ }, {
+ withoutStack = 4,
+ })
+ end, "Expected 4 warnings without a component stack but received 1")
+ end
+ )
+
+ it("fails if withoutStack is invalid", function()
+ -- deviation: null should error, but not undefined. Since they're the same
+ -- in Lua, we can comment the expectation
+ -- expectToWarnAndToThrow(function()
+ -- jestExpect(function()
+ -- console.warn("Hi")
+ -- end).toWarnDev("Hi", {withoutStack = nil})
+ -- end, "Instead received object")
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.warn("Hi")
+ end).toWarnDev("Hi", { withoutStack = {} })
+ end, "Instead received table")
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.warn("Hi")
+ end).toWarnDev("Hi", {
+ withoutStack = "haha",
+ })
+ end, "Instead received string")
+ end)
+
+ it("fails if the argument number does not match", function()
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.warn("Hi %s", "Sara", "extra")
+ end).toWarnDev("Hi", { withoutStack = true })
+ end, "Received 2 arguments for a message with 1 placeholders")
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.warn("Hi %s")
+ end).toWarnDev("Hi", { withoutStack = true })
+ end, "Received 0 arguments for a message with 1 placeholders")
+ end)
+
+ it("fails if stack is passed twice", function()
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.warn("Hi %s%s", "\n in div", "\n in div")
+ end).toWarnDev("Hi")
+ end, "Received more than one component stack for a warning")
+ end)
+
+ it("fails if multiple strings are passed without an array wrapper", function()
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.warn("Hi \n in div")
+ end).toWarnDev("Hi", "Bye")
+ end, "toWarnDev() second argument, when present, should be an object")
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.warn("Hi \n in div")
+ console.warn("Bye \n in div")
+ end).toWarnDev("Hi", "Bye")
+ end, "toWarnDev() second argument, when present, should be an object")
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.warn("Hi \n in div")
+ console.warn("Wow \n in div")
+ console.warn("Bye \n in div")
+ end).toWarnDev("Hi", "Bye")
+ end, "toWarnDev() second argument, when present, should be an object")
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.warn("Hi \n in div")
+ console.warn("Wow \n in div")
+ console.warn("Bye \n in div")
+ end).toWarnDev("Hi", "Wow", "Bye")
+ end, "toWarnDev() second argument, when present, should be an object")
+ end)
+
+ it("fails on more than two arguments", function()
+ expectToWarnAndToThrow(function()
+ jestExpect(function()
+ console.warn("Hi \n in div")
+ console.warn("Wow \n in div")
+ console.warn("Bye \n in div")
+ end).toWarnDev("Hi", nil, "Bye")
+ end, "toWarnDev() received more than two arguments.")
+ end)
+ end
+end)
+
+describe("toLogDev", function()
+ it("does not fail if a logs contains a stack", function()
+ jestExpect(function()
+ console.log("Hello\n in div")
+ end).toLogDev("Hello")
+ end)
+
+ it("does not fail if all logs contain a stack", function()
+ jestExpect(function()
+ console.log("Hello\n in div")
+ console.log("Good day\n in div")
+ console.log("Bye\n in div")
+ end).toLogDev({
+ "Hello",
+ "Good day",
+ "Bye",
+ })
+ end)
+end)
diff --git a/modules/roblox-jest/src/Matchers/createConsoleMatcher.lua b/WorkspaceStatic/jest/matchers/createConsoleMatcher.lua
similarity index 61%
rename from modules/roblox-jest/src/Matchers/createConsoleMatcher.lua
rename to WorkspaceStatic/jest/matchers/createConsoleMatcher.lua
index 8cd98eff..86ee5204 100644
--- a/modules/roblox-jest/src/Matchers/createConsoleMatcher.lua
+++ b/WorkspaceStatic/jest/matchers/createConsoleMatcher.lua
@@ -1,36 +1,42 @@
--- upstream https://github.com/facebook/react/blob/6d50a9d090a2a672fc3dea5ce77a3a05332a6caa/fixtures/legacy-jsx-runtimes/setupTests.js
---!nocheck
-local Packages = script.Parent.Parent.Parent
-local jestDiff = require(script.Parent.jestDiff)
--- deviation: until we can require jest, use a simple implementation
+--!nonstrict
+-- ROBLOX upstream: https://github.com/facebook/react/blob/6d50a9d090a2a672fc3dea5ce77a3a05332a6caa/fixtures/legacy-jsx-runtimes/setupTests.js
+local Packages = script.Parent.Parent.Parent.TestRunner
+local ReactGlobals = require(Packages.Dev.ReactGlobals)
+local JestDiff = require(Packages.Dev.JestDiff)
local function shouldIgnoreConsoleError(format, args)
-- deviation: instead of checking if `process.env.NODE_ENV ~= "production"`
-- we use the __DEV__ global
- if _G.__DEV__ then
+ if ReactGlobals.__DEV__ then
if typeof(format) == "string" then
- if format:find("Error: Uncaught [") == 0 then
+ if string.find(format, "Error: Uncaught %[") == 1 then
-- // This looks like an uncaught error from invokeGuardedCallback() wrapper
-- // in development that is reported by jsdom. Ignore because it's noisy.
return true
end
- if format:find("The above error occurred") == 0 then
+ -- ROBLOX FIXME: The "Warning: " prefix is applied before the string
+ -- reaches this function, which appears to not be the case upstream
+ if string.find(format, "Warning: The above error occurred") == 1 then
-- // This looks like an error addendum from ReactFiberErrorLogger.
-- // Ignore it too.
return true
end
end
else
- if format ~= nil and
- typeof(format.message) == "string" and
- typeof(format.stack) == "string"
+ if
+ format ~= nil
+ and typeof(format.message) == "string"
+ and typeof(format.stack) == "string"
and #args == 0
then
-- // In production, ReactFiberErrorLogger logs error objects directly.
-- // They are noisy too so we'll try to ignore them.
return true
end
- if format:find("act(...) is not supported in production builds of React") == 0 then
+ if
+ string.find(format, "act(...) is not supported in production builds of React")
+ == 0
+ then
-- // We don't yet support act() for prod builds, and warn for it.
-- // But we'd like to use act() ourselves for prod builds.
-- // Let's ignore the warning and #yolo.
@@ -48,7 +54,7 @@ local function normalizeCodeLocInfo(str)
-- // This special case exists only for the special source location in
-- // ReactElementValidator. That will go away if we remove source locations.
- str = str:gsub("Check your code at .*:%d+", "Check your code at **")
+ str = string.gsub(str, "Check your code at .*:%d+", "Check your code at **")
-- // V8 format:
-- // at Component (/path/filename.js:123:45)
-- // React format:
@@ -57,12 +63,12 @@ local function normalizeCodeLocInfo(str)
-- ROBLOX deviation: In roblox/luau, we're using the stack frame from luau,
-- which looks like:
-- in Component (at ModulePath.FileName.lua:123)
- return (str:gsub("\n in ([%w%-%._]+)[^\n]*", "\n in %1 (at **)"))
+ return (string.gsub(str, "\n in ([%w%-%._]+)[^\n]*", "\n in %1 (at **)"))
end
return function(consoleMethod, matcherName)
return function(_matcherContext, callback, expectedMessages, options, ...)
- local LuauPolyfill = require(Packages.LuauPolyfill)
+ local LuauPolyfill = require(Packages.Dev.LuauPolyfill)
local Array = LuauPolyfill.Array
local console = LuauPolyfill.console
@@ -71,30 +77,37 @@ return function(consoleMethod, matcherName)
end
-- deviation: instead of checking if `process.env.NODE_ENV ~= "production"`
-- we use the __DEV__ global
- if _G.__DEV__ then
+ if ReactGlobals.__DEV__ then
-- // Warn about incorrect usage of matcher.
if typeof(expectedMessages) == "string" then
- expectedMessages = {expectedMessages}
+ expectedMessages = { expectedMessages }
elseif not Array.isArray(expectedMessages) then
error(
- ("%s() requires a parameter of type string or an array of strings "):format(matcherName)
- .. ("but was given %s."):format(typeof(expectedMessages))
+ string.format(
+ "%s() requires a parameter of type string or an array of strings ",
+ matcherName
+ )
+ .. string.format("but was given %s.", typeof(expectedMessages))
)
end
-- deviation: since an empty table will return true for
-- `Array.isArray(options)`, check if the table is not empty
- if typeof(options) ~= "table" or
- (Array.isArray(options) and next(options) ~= nil)
+ if
+ typeof(options) ~= "table"
+ or (Array.isArray(options) and next(options) ~= nil)
then
error(
- ("%s() second argument, when present, should be an object. "):format(matcherName) ..
- "Did you forget to wrap the messages into an array?"
+ string.format(
+ "%s() second argument, when present, should be an object. ",
+ matcherName
+ )
+ .. "Did you forget to wrap the messages into an array?"
)
end
if select("#", ...) > 0 then
error(
- ("%s() received more than two arguments. "):format(matcherName) ..
- "Did you forget to wrap the messages into an array?"
+ string.format("%s() received more than two arguments. ", matcherName)
+ .. "Did you forget to wrap the messages into an array?"
)
end
@@ -114,33 +127,36 @@ return function(consoleMethod, matcherName)
local caughtError
local function isLikelyAComponentStack(message)
- return typeof(message) == "string" and
- message:match('\n in ') ~= nil
+ return typeof(message) == "string"
+ and string.match(message, "\n in ") ~= nil
end
local function consoleSpy(format, ...)
-- // Ignore uncaught errors reported by jsdom
-- // and React addendums because they're too noisy.
- local args = {...}
- if not logAllErrors and
- consoleMethod == 'error' and
- shouldIgnoreConsoleError(format, args)
+ local args = { ... }
+ if
+ not logAllErrors
+ and consoleMethod == "error"
+ and shouldIgnoreConsoleError(format, args)
then
return
end
local message = format
- local formattedOk, formattedOrError = pcall(string.format, format, unpack(args))
+ local formattedOk, formattedOrError =
+ pcall(string.format, format, unpack(args))
if formattedOk then
message = formattedOrError
end
local normalizedMessage = normalizeCodeLocInfo(message)
- -- // Remember if the number of %s interpolations
- -- // doesn't match the number of arguments.
- -- // We'll fail the test if it happens.
+ -- Remember if the number of %s interpolations
+ -- doesn't match the number of arguments.
+ -- We'll fail the test if it happens.
local argIndex = 0
- format:gsub("%%s", function()
+ -- ROBLOX FIXME selene: remove _ assignment when bug in selene is fixed https://github.com/Kampfkarren/selene/issues/406
+ local _ = string.gsub(format, "%%s", function()
argIndex = argIndex + 1
return argIndex - 1
end)
@@ -155,17 +171,20 @@ return function(consoleMethod, matcherName)
-- // Protect against accidentally passing a component stack
-- // to warning() which already injects the component stack.
- if #args >= 2 and
- isLikelyAComponentStack(args[#args]) and
- isLikelyAComponentStack(args[#args - 1])
+ if
+ #args >= 2
+ and isLikelyAComponentStack(args[#args])
+ and isLikelyAComponentStack(args[#args - 1])
then
- lastWarningWithExtraComponentStack = {format = format}
+ lastWarningWithExtraComponentStack = { format = format }
end
- for index=1, #expectedMessages do
+ for index = 1, #expectedMessages do
local expectedMessage = expectedMessages[index]
- if normalizedMessage == expectedMessage or
- normalizedMessage:find(expectedMessage, 1, true) ~= nil
+ if
+ normalizedMessage == expectedMessage
+ or string.find(normalizedMessage, expectedMessage, 1, true)
+ ~= nil
then
if isLikelyAComponentStack(normalizedMessage) then
table.insert(warningsWithComponentStack, normalizedMessage)
@@ -179,11 +198,15 @@ return function(consoleMethod, matcherName)
local errorMessage
if #expectedMessages == 0 then
- errorMessage = 'Unexpected warning recorded: ' .. normalizedMessage
+ errorMessage = "Unexpected warning recorded: " .. normalizedMessage
elseif #expectedMessages == 1 then
- errorMessage = 'Unexpected warning recorded: ' .. jestDiff(expectedMessages[1], normalizedMessage)
+ errorMessage = "Unexpected warning recorded: "
+ .. tostring(JestDiff.diff(expectedMessages[1], normalizedMessage))
else
- errorMessage = 'Unexpected warning recorded: ' .. jestDiff(expectedMessages, {normalizedMessage})
+ errorMessage = "Unexpected warning recorded: "
+ .. tostring(
+ JestDiff.diff(expectedMessages, { normalizedMessage })
+ )
end
-- // Record the call stack for unexpected warnings.
@@ -229,7 +252,8 @@ return function(consoleMethod, matcherName)
if #expectedMessages > 0 then
return {
message = function()
- return ("Expected warning was not recorded: %s\n "):format(
+ return string.format(
+ "Expected warning was not recorded: %s\n ",
expectedMessages[1]
)
end,
@@ -243,10 +267,10 @@ return function(consoleMethod, matcherName)
local warnings = warningsWithoutComponentStack
return {
message = function()
- return ("Expected %s warnings without a component stack but received %s:\n"):format(
+ return ("Expected %d warnings without a component stack but received %d:\n"):format(
withoutStack,
#warningsWithoutComponentStack
- ) .. table.concat(warnings, "\n")
+ ) .. table.concat(warnings, "\n")
end,
pass = false,
}
@@ -255,12 +279,17 @@ return function(consoleMethod, matcherName)
if #warningsWithComponentStack > 0 then
return {
message = function()
- return "Received warning unexpectedly includes a component stack:\n" ..
- (" %s\nIf this warning intentionally includes the component stack, remove ")
- :format(warningsWithComponentStack[1]) ..
- ("{withoutStack: true} from the %s() call. If you have a mix of "):format(matcherName) ..
- ("warnings with and without stack in one %s() call, pass "):format(matcherName) ..
- ("{withoutStack: N} where N is the number of warnings without stacks."):format(matcherName)
+ return "Received warning unexpectedly includes a component stack:\n"
+ .. (" %s\nIf this warning intentionally includes the component stack, remove "):format(
+ warningsWithComponentStack[1]
+ )
+ .. ("{withoutStack: true} from the %s() call. If you have a mix of "):format(
+ matcherName
+ )
+ .. ("warnings with and without stack in one %s() call, pass "):format(
+ matcherName
+ )
+ .. "{withoutStack: N} where N is the number of warnings without stacks."
end,
pass = false,
}
@@ -271,25 +300,32 @@ return function(consoleMethod, matcherName)
if #warningsWithoutComponentStack > 0 then
return {
message = function()
- return "Received warning unexpectedly does not include a component stack:\n" ..
- (" %s\nIf this warning intentionally omits the component stack, add "):format(warningsWithoutComponentStack[1]) ..
- ("{withoutStack: true} to the %s call."):format(matcherName)
+ return "Received warning unexpectedly does not include a component stack:\n"
+ .. (" %s\nIf this warning intentionally omits the component stack, add "):format(
+ warningsWithoutComponentStack[1]
+ )
+ .. string.format(
+ "{withoutStack: true} to the %s call.",
+ matcherName
+ )
end,
pass = false,
}
end
else
error(
- ("The second argument for %s(), when specified, must be an object. It may have a "):format(matcherName) ..
- 'property called "withoutStack" whose value may be undefined, boolean, or a number. ' ..
- ("Instead received %s."):format(typeof(withoutStack))
+ ("The second argument for %s(), when specified, must be an object. It may have a "):format(
+ matcherName
+ )
+ .. 'property called "withoutStack" whose value may be undefined, boolean, or a number. '
+ .. string.format("Instead received %s.", typeof(withoutStack))
)
end
if lastWarningWithMismatchingFormat ~= nil then
return {
message = function()
- return ("Received %s arguments for a message with %s placeholders:\n %s"):format(
+ return ("Received %d arguments for a message with %s placeholders:\n %s"):format(
#lastWarningWithMismatchingFormat.args,
lastWarningWithMismatchingFormat.expectedArgCount,
lastWarningWithMismatchingFormat.format
@@ -301,21 +337,22 @@ return function(consoleMethod, matcherName)
if lastWarningWithExtraComponentStack ~= nil then
return {
message = function()
- return "Received more than one component stack for a warning:\n" ..
- (" %s\nDid you accidentally pass a stack to warning() as the last argument? ")
- :format(lastWarningWithExtraComponentStack.format) ..
- "Don't forget warning() already injects the component stack automatically."
+ return "Received more than one component stack for a warning:\n"
+ .. (" %s\nDid you accidentally pass a stack to warning() as the last argument? "):format(
+ lastWarningWithExtraComponentStack.format
+ )
+ .. "Don't forget warning() already injects the component stack automatically."
end,
pass = false,
}
end
- return {pass = true}
+ return { pass = true }
else
-- // Any uncaught errors or warnings should fail tests in production mode.
callback()
- return {pass = true}
+ return { pass = true }
end
end
end
diff --git a/WorkspaceStatic/jest/matchers/interactionTracingMatchers.lua b/WorkspaceStatic/jest/matchers/interactionTracingMatchers.lua
new file mode 100644
index 00000000..0589b605
--- /dev/null
+++ b/WorkspaceStatic/jest/matchers/interactionTracingMatchers.lua
@@ -0,0 +1,136 @@
+--!nonstrict
+-- ROBLOX upstream https://github.com/facebook/react/blob/v17.0.1/scripts/jest/matchers/interactionTracingMatchers.js
+local Packages = script.Parent.Parent.Parent.TestRunner
+local JestDiff = require(Packages.Dev.JestDiff)
+local LuauPolyfill = require(Packages.Dev.LuauPolyfill)
+local Object = LuauPolyfill.Object
+-- ROBLOX deviation: hoist and pre-declare functions
+local toContainNoInteractions, toHaveBeenLastNotifiedOfWork, toHaveBeenLastNotifiedOfInteraction, toMatchInteraction, toMatchInteractions
+
+function toContainNoInteractions(self, actualSet)
+ return {
+ message = function()
+ if self.isNot then
+ return "Expected interactions but there were none."
+ end
+ return "Expected no interactions but there were "
+ .. tostring(#Object.keys(actualSet))
+ .. "."
+ end,
+ pass = #actualSet == 0,
+ }
+end
+
+function toHaveBeenLastNotifiedOfInteraction(self, mockFunction, expectedInteraction)
+ local calls = mockFunction.mock.calls
+ if #calls == 0 then
+ return {
+ message = function()
+ return "Mock function was not called"
+ end,
+ pass = false,
+ }
+ end
+
+ local actualInteraction = calls[#calls][1]
+
+ return toMatchInteraction(self, actualInteraction, expectedInteraction)
+end
+
+function toHaveBeenLastNotifiedOfWork(
+ self,
+ mockFunction,
+ expectedInteractions,
+ expectedThreadID
+)
+ local calls = mockFunction.mock.calls
+ if #calls == 0 then
+ return {
+ message = function()
+ return "Mock function was not called"
+ end,
+ pass = false,
+ }
+ end
+
+ local actualInteractions, actualThreadID = calls[#calls][1], calls[#calls][2]
+
+ if expectedThreadID ~= nil then
+ if expectedThreadID ~= actualThreadID then
+ return {
+ message = function()
+ return JestDiff.diff(
+ tostring(expectedThreadID),
+ tostring(actualThreadID)
+ )
+ end,
+ pass = false,
+ }
+ end
+ end
+
+ return toMatchInteractions(self, actualInteractions, expectedInteractions)
+end
+
+function toMatchInteraction(self, actual, expected)
+ for _, attribute in expected do
+ if actual[attribute] ~= expected[attribute] then
+ return {
+ message = function()
+ return JestDiff.diff(expected, actual)
+ end,
+
+ pass = false,
+ }
+ end
+ end
+
+ return { pass = true }
+end
+
+function toMatchInteractions(self, actualSetOrArray, expectedSetOrArray)
+ local actualArrayLength = 0
+ for _, __ in actualSetOrArray do
+ actualArrayLength += 1
+ end
+
+ local expectedArrayLength = 0
+ for _, __ in expectedSetOrArray do
+ expectedArrayLength += 1
+ end
+
+ if actualArrayLength ~= expectedArrayLength then
+ return {
+ message = function()
+ return string.format(
+ "Expected %d interactions but there were %d",
+ expectedArrayLength,
+ actualArrayLength
+ )
+ end,
+ pass = false,
+ }
+ end
+
+ for i, actualInteraction in actualSetOrArray do
+ local expectedInteraction = if expectedSetOrArray._array
+ then expectedSetOrArray._array[i]
+ else expectedSetOrArray[i]
+ local result = toMatchInteraction(self, actualInteraction, expectedInteraction)
+ if result.pass == false then
+ return result
+ end
+ end
+
+ return { pass = true }
+end
+
+local exports = {
+ toContainNoInteractions = toContainNoInteractions,
+ toHaveBeenLastNotifiedOfInteraction = toHaveBeenLastNotifiedOfInteraction,
+ toHaveBeenLastNotifiedOfWork = toHaveBeenLastNotifiedOfWork,
+ toMatchInteraction = toMatchInteraction,
+ toMatchInteractions = toMatchInteractions,
+}
+
+return exports
diff --git a/WorkspaceStatic/jest/matchers/reactTestMatchers.lua b/WorkspaceStatic/jest/matchers/reactTestMatchers.lua
new file mode 100644
index 00000000..308d6b19
--- /dev/null
+++ b/WorkspaceStatic/jest/matchers/reactTestMatchers.lua
@@ -0,0 +1,108 @@
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.2/scripts/jest/matchers/reactTestMatchers.js
+local Packages = script.Parent.Parent.Parent.TestRunner
+-- ROBLOX deviation START: fix import
+-- local LuauPolyfill = require(Packages.LuauPolyfill)
+local LuauPolyfill = require(Packages.Dev.LuauPolyfill)
+-- ROBLOX deviation END
+-- ROBLOX deviation START: not used
+-- local Error = LuauPolyfill.Error
+-- ROBLOX deviation END
+local Object = LuauPolyfill.Object
+local JestGlobals = require(Packages.Dev.JestGlobals)
+local expect = JestGlobals.expect
+-- ROBLOX deviation START: fix import
+-- local JestReact = require_("jest-react")
+-- local SchedulerMatchers = require_("./schedulerTestMatchers")
+local JestReact = require(Packages.Dev.JestReact)
+local SchedulerMatchers = require(script.Parent.schedulerTestMatchers)
+-- ROBLOX deviation END
+-- ROBLOX deviation START: add return type
+-- local function captureAssertion(fn)
+local function captureAssertion(
+ fn
+): { pass: false, message: () -> string } | { pass: true }
+ -- ROBLOX deviation END
+ -- Trick to use a Jest matcher inside another Jest matcher. `fn` contains an
+ -- assertion; if it throws, we capture the error and return it, so the stack
+ -- trace presented to the user points to the original assertion in the
+ -- test file.
+ do --[[ ROBLOX COMMENT: try-catch block conversion ]]
+ -- ROBLOX deviation START: use pcall, simplify
+ -- local ok, result, hasReturned = xpcall(function()
+ -- fn()
+ -- end, function(error_)
+ -- return {
+ -- pass = false,
+ -- message = function()
+ -- return error_.message
+ -- end,
+ -- },
+ -- true
+ -- end)
+ -- if hasReturned then
+ -- return result
+ -- end
+ local ok, result = pcall(fn)
+
+ if not ok then
+ return {
+ pass = false,
+ message = function()
+ return tostring(result)
+ end,
+ }
+ end
+ -- ROBLOX deviation END
+ end
+ return { pass = true }
+end
+
+local function assertYieldsWereCleared(Scheduler)
+ -- ROBLOX deviation START: use dot notation
+ -- local actualYields = Scheduler:unstable_clearYields()
+ local actualYields = Scheduler.unstable_clearYields()
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: fix .length
+ -- if actualYields.length ~= 0 then
+ if #actualYields ~= 0 then
+ -- ROBLOX deviation END
+ error(
+ -- ROBLOX deviation START: adapt error message, error with string
+ -- Error.new(
+ -- "Log of yielded values is not empty. "
+ -- .. "Call expect(Scheduler).toHaveYielded(...) first."
+ -- )
+ "Log of yielded values is not empty. "
+ .. "Call expectToHaveYielded(Scheduler, ...) first.",
+ 3
+ -- ROBLOX deviation END
+ )
+ end
+end
+-- ROBLOX deviation START: add context argument
+-- local function toMatchRenderedOutput(ReactNoop, expectedJSX)
+local function toMatchRenderedOutput(_matcherContext, ReactNoop, expectedJSX)
+ -- ROBLOX deviation END
+ if typeof(ReactNoop.getChildrenAsJSX) == "function" then
+ local Scheduler = ReactNoop._Scheduler
+ assertYieldsWereCleared(Scheduler)
+ return captureAssertion(function()
+ -- ROBLOX deviation START: use dot notation
+ -- expect(ReactNoop:getChildrenAsJSX()).toEqual(expectedJSX)
+ expect(ReactNoop.getChildrenAsJSX()).toEqual(expectedJSX)
+ -- ROBLOX deviation END
+ end)
+ end
+ -- ROBLOX deviation START: use dot notation
+ -- return JestReact:unstable_toMatchRenderedOutput(ReactNoop, expectedJSX)
+ return JestReact.unstable_toMatchRenderedOutput(ReactNoop, expectedJSX)
+ -- ROBLOX deviation END
+end
+-- ROBLOX deviation START: replace module.exports
+-- module.exports = Object.assign(
+return Object.assign(
+ -- ROBLOX deviation END
+ {},
+ SchedulerMatchers,
+ { toMatchRenderedOutput = toMatchRenderedOutput }
+)
diff --git a/WorkspaceStatic/jest/matchers/schedulerTestMatchers.lua b/WorkspaceStatic/jest/matchers/schedulerTestMatchers.lua
new file mode 100644
index 00000000..65eae617
--- /dev/null
+++ b/WorkspaceStatic/jest/matchers/schedulerTestMatchers.lua
@@ -0,0 +1,212 @@
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.2/scripts/jest/matchers/schedulerTestMatchers.js
+local Packages = script.Parent.Parent.Parent.TestRunner
+-- ROBLOX deviation START: fix import
+-- local LuauPolyfill = require(Packages.LuauPolyfill)
+local LuauPolyfill = require(Packages.Dev.LuauPolyfill)
+-- ROBLOX deviation END
+-- ROBLOX deviation START: not used
+-- local Array = LuauPolyfill.Array
+-- local Error = LuauPolyfill.Error
+-- ROBLOX deviation END
+local JestGlobals = require(Packages.Dev.JestGlobals)
+local expect = JestGlobals.expect
+-- ROBLOX deviation START: add import
+type Array = LuauPolyfill.Array
+-- ROBLOX deviation END
+-- ROBLOX FIXME Luau: have to have explicit annotation as workaround for CLI-50002
+-- ROBLOX deviation START: add return type
+-- local function captureAssertion(fn)
+local function captureAssertion(fn): { pass: boolean, message: (() -> string)? }
+ -- ROBLOX deviation END
+ -- Trick to use a Jest matcher inside another Jest matcher. `fn` contains an
+ -- assertion; if it throws, we capture the error and return it, so the stack
+ -- trace presented to the user points to the original assertion in the
+ -- test file.
+ do --[[ ROBLOX COMMENT: try-catch block conversion ]]
+ -- ROBLOX deviation START: replace xpcall, addapt message to something useful
+ -- local ok, result, hasReturned = xpcall(function()
+ -- fn()
+ -- end, function(error_)
+ -- return {
+ -- pass = false,
+ -- message = function()
+ -- return error_.message
+ -- end,
+ -- },
+ -- true
+ -- end)
+ -- if hasReturned then
+ -- return result
+ -- end
+ local ok, result = pcall(fn)
+ if not ok then
+ local stringResult = tostring(result)
+ local subMessageIndex = string.find(stringResult, " ")
+ assert(
+ subMessageIndex ~= nil,
+ "assertion failure text wasn't in expected format"
+ )
+ local message = string.sub(stringResult, subMessageIndex + 1)
+
+ return {
+ pass = false,
+ message = function()
+ return message
+ end,
+ }
+ end
+ -- ROBLOX deviation END
+ end
+ return { pass = true }
+end
+local function assertYieldsWereCleared(Scheduler)
+ -- ROBLOX deviation START: use dot notation
+ -- local actualYields = Scheduler:unstable_clearYields()
+ local actualYields = Scheduler.unstable_clearYields()
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: fix .length
+ -- if actualYields.length ~= 0 then
+ if #actualYields ~= 0 then
+ -- ROBLOX deviation END
+ error(
+ -- ROBLOX deviation START: adapt error message, error with string
+ -- Error.new(
+ -- "Log of yielded values is not empty. "
+ -- .. "Call expect(Scheduler).toHaveYielded(...) first."
+ -- )
+ "Log of yielded values is not empty. "
+ .. "Call expectToHaveYielded(Scheduler, ...) first.",
+ 3
+ -- ROBLOX deviation END
+ )
+ end
+end
+
+-- ROBLOX FIXME Luau: Array annotation here is so we don't have to put the annotation in many places due to mixed arrays
+-- ROBLOX deviation START: add context argument
+-- local function toFlushAndYield(Scheduler, expectedYields)
+local function toFlushAndYield(_matcherContext, Scheduler, expectedYields: Array)
+ -- ROBLOX deviation END
+ assertYieldsWereCleared(Scheduler)
+ -- ROBLOX deviation START: use dot notation
+ -- Scheduler:unstable_flushAllWithoutAsserting()
+ -- local actualYields = Scheduler:unstable_clearYields()
+ Scheduler.unstable_flushAllWithoutAsserting()
+ local actualYields = Scheduler.unstable_clearYields()
+ -- ROBLOX deviation END
+ return captureAssertion(function()
+ expect(actualYields).toEqual(expectedYields)
+ end)
+end
+
+-- ROBLOX FIXME Luau: Array annotation here is so we don't have to put the annotation in many places due to mixed arrays
+-- ROBLOX deviation START: add context argument
+-- local function toFlushAndYieldThrough(Scheduler, expectedYields)
+local function toFlushAndYieldThrough(
+ _matcherContext,
+ Scheduler,
+ expectedYields: Array
+)
+ -- ROBLOX deviation END
+ assertYieldsWereCleared(Scheduler)
+ -- ROBLOX deviation START: use dot notation
+ -- Scheduler:unstable_flushNumberOfYields(expectedYields.length)
+ -- local actualYields = Scheduler:unstable_clearYields()
+ Scheduler.unstable_flushNumberOfYields(#expectedYields)
+ local actualYields = Scheduler.unstable_clearYields()
+ -- ROBLOX deviation END
+ return captureAssertion(function()
+ expect(actualYields).toEqual(expectedYields)
+ end)
+end
+-- ROBLOX FIXME Luau: Array annotation here is so we don't have to put the annotation in many places due to mixed arrays
+-- ROBLOX deviation START: add context argument
+-- local function toFlushUntilNextPaint(Scheduler, expectedYields)
+local function toFlushUntilNextPaint(
+ _matcherContext,
+ Scheduler,
+ expectedYields: Array
+)
+ -- ROBLOX deviation END
+ assertYieldsWereCleared(Scheduler)
+ -- ROBLOX deviation START: use dot notation
+ -- Scheduler:unstable_flushUntilNextPaint()
+ -- local actualYields = Scheduler:unstable_clearYields()
+ Scheduler.unstable_flushUntilNextPaint()
+ local actualYields = Scheduler.unstable_clearYields()
+ -- ROBLOX deviation END
+ return captureAssertion(function()
+ expect(actualYields).toEqual(expectedYields)
+ end)
+end
+-- ROBLOX deviation START: add context argument
+-- local function toFlushWithoutYielding(Scheduler)
+local function toFlushWithoutYielding(_matcherContext, Scheduler)
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: add context argument
+ -- return toFlushAndYield(Scheduler, {})
+ return toFlushAndYield(_matcherContext, Scheduler, {})
+ -- ROBLOX deviation END
+end
+
+-- ROBLOX FIXME Luau: Array annotation here is so we don't have to put the annotation in many places due to mixed arrays
+-- ROBLOX deviation START: add context argument
+-- local function toFlushExpired(Scheduler, expectedYields)
+local function toFlushExpired(_matcherContext, Scheduler, expectedYields: Array)
+ -- ROBLOX deviation END
+ assertYieldsWereCleared(Scheduler)
+ -- ROBLOX deviation START: use dot notation
+ -- Scheduler:unstable_flushExpired()
+ -- local actualYields = Scheduler:unstable_clearYields()
+ Scheduler.unstable_flushExpired()
+ local actualYields = Scheduler.unstable_clearYields()
+ -- ROBLOX deviation END
+ return captureAssertion(function()
+ expect(actualYields).toEqual(expectedYields)
+ end)
+end
+
+-- ROBLOX FIXME Luau: Array annotation here is so we don't have to put the annotation in many places due to mixed arrays
+-- ROBLOX deviation START: add context argument
+-- local function toHaveYielded(Scheduler, expectedYields)
+local function toHaveYielded(_matcherContext, Scheduler, expectedYields: Array)
+ -- ROBLOX deviation END
+ return captureAssertion(function()
+ -- ROBLOX deviation START: use dot notation
+ -- local actualYields = Scheduler:unstable_clearYields()
+ local actualYields = Scheduler.unstable_clearYields()
+ -- ROBLOX deviation END
+ expect(actualYields).toEqual(expectedYields)
+ end)
+end
+-- ROBLOX deviation START: add context argument
+-- local function toFlushAndThrow(
+-- Scheduler,
+-- ...: any --[[ ROBLOX CHECK: check correct type of elements. ]]
+-- )
+local function toFlushAndThrow(_matcherContext, Scheduler, ...)
+ -- ROBLOX deviation END
+ local rest = { ... }
+ assertYieldsWereCleared(Scheduler)
+ return captureAssertion(function()
+ -- ROBLOX TODO Luau: if we wrap this function, we get an odd analyze error: Type '() -> ()' could not be converted into '{| |}'
+ -- ROBLOX deviation START: use dot notation, fix spreading
+ -- expect(function()
+ -- Scheduler:unstable_flushAllWithoutAsserting()
+ -- end).toThrow(table.unpack(Array.spread(rest)))
+ expect(Scheduler.unstable_flushAllWithoutAsserting).toThrow(table.unpack(rest))
+ -- ROBLOX deviation END
+ end)
+end
+-- ROBLOX deviation START: replace module.exports
+-- module.exports = {
+return {
+ -- ROBLOX deviation END
+ toFlushAndYield = toFlushAndYield,
+ toFlushAndYieldThrough = toFlushAndYieldThrough,
+ toFlushUntilNextPaint = toFlushUntilNextPaint,
+ toFlushWithoutYielding = toFlushWithoutYielding,
+ toFlushExpired = toFlushExpired,
+ toHaveYielded = toHaveYielded,
+ toFlushAndThrow = toFlushAndThrow,
+}
diff --git a/modules/roblox-jest/src/Matchers/toErrorDev.lua b/WorkspaceStatic/jest/matchers/toErrorDev.lua
similarity index 100%
rename from modules/roblox-jest/src/Matchers/toErrorDev.lua
rename to WorkspaceStatic/jest/matchers/toErrorDev.lua
diff --git a/WorkspaceStatic/jest/matchers/toLogDev.lua b/WorkspaceStatic/jest/matchers/toLogDev.lua
new file mode 100644
index 00000000..082beca3
--- /dev/null
+++ b/WorkspaceStatic/jest/matchers/toLogDev.lua
@@ -0,0 +1,3 @@
+local createConsoleMatcher = require(script.Parent.createConsoleMatcher)
+
+return createConsoleMatcher("log", "toLogDev")
diff --git a/modules/roblox-jest/src/Matchers/toWarnDev.lua b/WorkspaceStatic/jest/matchers/toWarnDev.lua
similarity index 100%
rename from modules/roblox-jest/src/Matchers/toWarnDev.lua
rename to WorkspaceStatic/jest/matchers/toWarnDev.lua
diff --git a/WorkspaceStatic/jest/testSetupFile.lua b/WorkspaceStatic/jest/testSetupFile.lua
new file mode 100644
index 00000000..aee2b968
--- /dev/null
+++ b/WorkspaceStatic/jest/testSetupFile.lua
@@ -0,0 +1,24 @@
+-- ROBLOX note: no upstream
+-- loosely based on https://github.com/facebook/react/blob/9abc2785cb070148d64fae81e523246b90b92016/scripts/jest/setupTests.js
+-- in a way that we use this file to extend jestExpect with custom matchers
+
+local Packages = script.Parent.Parent.TestRunner
+local JestGlobals = require(Packages.Dev.JestGlobals)
+local jestExpect = JestGlobals.expect
+
+local InteractionTracingMatchers =
+ require(script.Parent.matchers.interactionTracingMatchers)
+
+jestExpect.extend(require(script.Parent.matchers.reactTestMatchers))
+jestExpect.extend({
+ toErrorDev = require(script.Parent.matchers.toErrorDev),
+ toWarnDev = require(script.Parent.matchers.toWarnDev),
+ toLogDev = require(script.Parent.matchers.toLogDev),
+ toContainNoInteractions = InteractionTracingMatchers.toContainNoInteractions,
+ toHaveBeenLastNotifiedOfInteraction = InteractionTracingMatchers.toHaveBeenLastNotifiedOfInteraction,
+ toHaveBeenLastNotifiedOfWork = InteractionTracingMatchers.toHaveBeenLastNotifiedOfWork,
+ toMatchInteraction = InteractionTracingMatchers.toMatchInteraction,
+ toMatchInteractions = InteractionTracingMatchers.toMatchInteractions,
+})
+
+_G.jest = true
diff --git a/bin/ci-benchmarks.sh b/bin/ci-benchmarks.sh
new file mode 100755
index 00000000..9f74c3d6
--- /dev/null
+++ b/bin/ci-benchmarks.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+set -x
+
+rotrieve install
+rojo build tests.project.json --output model.rbxm
+
+echo "Remove .robloxrc from dependencies"
+find Packages/_Index -name "*.robloxrc" | xargs rm -f
+
+echo "Run static analysis"
+roblox-cli analyze tests.project.json
+selene --version
+selene --config selene.toml modules/
+stylua --version
+stylua -c modules -g "*[a-bdh-km-oquvyz].lua"
+
+echo "Run benchmarks"
+robloxdev-cli run --load.model model.rbxm --run bin/run-first-render-benchmark.lua --fastFlags.allOnLuau --fastFlags.overrides EnableLoadModule=true EnableDelayedTaskMethods=true --headlessRenderer 1
+robloxdev-cli run --load.model model.rbxm --run bin/run-frame-rate-benchmark.lua --fastFlags.allOnLuau --fastFlags.overrides EnableLoadModule=true EnableDelayedTaskMethods=true --headlessRenderer 1
+robloxdev-cli run --load.model model.rbxm --run bin/run-deep-tree-benchmark.lua --fastFlags.allOnLuau --fastFlags.overrides EnableLoadModule=true EnableDelayedTaskMethods=true --headlessRenderer 1
+robloxdev-cli run --load.model model.rbxm --run bin/run-wide-tree-benchmark.lua --fastFlags.allOnLuau --fastFlags.overrides EnableLoadModule=true EnableDelayedTaskMethods=true --headlessRenderer 1
+robloxdev-cli run --load.model model.rbxm --run bin/run-sierpinski-triangle-benchmark.lua --fastFlags.allOnLuau --fastFlags.overrides EnableLoadModule=true EnableDelayedTaskMethods=true --headlessRenderer 1
diff --git a/bin/ci.sh b/bin/ci.sh
index 0f751f60..ebb557ba 100755
--- a/bin/ci.sh
+++ b/bin/ci.sh
@@ -3,17 +3,28 @@
set -x
rotrieve install
-rojo build tests.project.json --output model.rbxmx
echo "Remove .robloxrc from dependencies"
find Packages/_Index -name "*.robloxrc" | xargs rm -f
-roblox-cli analyze tests.project.json
+echo "Run static analysis"
+roblox-cli analyze --project tests.project.json
selene --version
-selene --config selene.toml modules/
+selene --config selene.toml modules/ WorkspaceStatic/
stylua --version
-stylua -c modules -g "*[a-bdh-km-oquvyz].lua"
+stylua -c modules bin WorkspaceStatic
+
echo "Run tests in DEV"
-roblox-cli run --load.model model.rbxmx --run bin/spec.lua --fastFlags.overrides EnableLoadModule=true --fastFlags.allOnLuau --lua.globals=__DEV__=true
+robloxdev-cli run --load.model tests.project.json \
+ --run bin/spec.lua \
+ --fastFlags.allOnLuau --fastFlags.overrides UseDateTimeType3=true EnableLoadModule=true DebugDisableOptimizedBytecode=true EnableDelayedTaskMethods=true MaxDeferReentrancyDepth=65 \
+ --load.asRobloxScript --headlessRenderer 1 --virtualInput 1 --fs.readwrite=$PWD --lua.globals=__COMPAT_WARNINGS__=true \
+ --lua.globals=UPDATESNAPSHOT=false --lua.globals=CI=true --lua.globals=__ROACT_17_MOCK_SCHEDULER__=true \
+ --lua.globals=__DEV__=true
+
echo "Run tests in release"
-roblox-cli run --load.model model.rbxmx --run bin/spec.lua --fastFlags.overrides EnableLoadModule=true --fastFlags.allOnLuau
+robloxdev-cli run --load.model tests.project.json \
+ --run bin/spec.lua \
+ --fastFlags.allOnLuau --fastFlags.overrides UseDateTimeType3=true EnableLoadModule=true DebugDisableOptimizedBytecode=true EnableDelayedTaskMethods=true MaxDeferReentrancyDepth=65 \
+ --load.asRobloxScript --headlessRenderer 1 --virtualInput 1 --fs.readwrite=$PWD \
+ --lua.globals=UPDATESNAPSHOT=false --lua.globals=CI=true --lua.globals=__ROACT_17_MOCK_SCHEDULER__=true
diff --git a/bin/compare-benchmarks.py b/bin/compare-benchmarks.py
new file mode 100644
index 00000000..fb48f386
--- /dev/null
+++ b/bin/compare-benchmarks.py
@@ -0,0 +1,81 @@
+#!/usr/bin/python
+
+# This script creates a CSV comparing the difference between two benchmark CSVs
+# Pass --output or -o to set an output dir, helpful for comparisons and organization (ex: -o bin/featureName-benchmarks)
+# Pass the two CSVs as direct args, no --flag-prefix needed
+
+import os
+import sys
+import re
+
+def percentChange(a, b):
+ a = a if type(a) == float else float(a)
+ b = b if type(b) == float else float(b)
+ return (b-a)/abs(b) * 100
+
+CSVs = []
+
+# Default parameters
+parameters = {
+ "directory": "bin/benchmarks",
+}
+
+# Parse command line arguments
+argNum = 1
+while argNum < len(sys.argv):
+ arg = sys.argv[argNum]
+ if arg == "-o" or arg == "--output":
+ value = sys.argv[argNum+1]
+ if value[0:1] != "-":
+ parameters['directory'] = value
+ else:
+ print(f"Error: Argument for {arg} is missing, please specify an output directory")
+ exit(1)
+
+ argNum += 2
+ elif arg[0:1] == "-":
+ print(f"Error: Unsupported flag {arg}")
+ exit(1)
+ else:
+ CSVs.append(arg)
+ argNum += 1
+
+csvA = open(CSVs[0], mode="r", encoding="utf-8")
+csvB = open(CSVs[1], mode="r", encoding="utf-8")
+
+aName = re.search(r"([\w\-]+)\.csv$", CSVs[0]).group(1).strip().replace("-benchmark", "")
+bName = re.search(r"([\w\-]+)\.csv$", CSVs[1]).group(1).strip().replace("-benchmark", "")
+
+csvALines = csvA.readlines()
+csvBLines = csvB.readlines()
+
+# Create the results directory
+if not os.path.exists(parameters['directory']):
+ os.makedirs(parameters['directory'])
+
+outputFile = open(f"{parameters['directory']}/compare-{aName}-to-{bName}-benchmark.csv", mode="w", encoding="utf-8")
+outputFile.write(f"Test,Metric,{aName},{bName},Unit,Change")
+outputFile.write('\n')
+
+headers = {}
+for i, line in enumerate(csvALines):
+ if i == 0:
+ rawHeaders = line.split(",")
+ for idx, header in enumerate(rawHeaders):
+ headers[header] = idx
+ continue
+
+ aValues = line.split(",")
+ bValues = csvBLines[i].split(",")
+
+ outputFile.write("{test},{metric},{a},{b},{unit},{change:.3f}%".format(
+ test=aValues[headers['Test']],
+ metric=aValues[headers['Metric']],
+ a = aValues[headers['Value']],
+ b = bValues[headers['Value']],
+ unit = aValues[headers['Unit']],
+ change = percentChange(aValues[headers['Value']], bValues[headers['Value']]),
+ ))
+ outputFile.write('\n')
+
+outputFile.close()
diff --git a/bin/run-benchmarks.py b/bin/run-benchmarks.py
new file mode 100644
index 00000000..4c3e9f0e
--- /dev/null
+++ b/bin/run-benchmarks.py
@@ -0,0 +1,204 @@
+#!/usr/bin/python
+
+# This script runs all the benchmarks, and outputs:
+# - Flamegraphs of each benchmark
+# - A detailed csv of all the processed results
+# File names will include your git branch as a prefix, making it easier to compare changes.
+#
+# Pass --output or -o to set an output dir, helpful for comparisons and organization (ex: -o bin/featureName-benchmarks)
+# Pass --runs or -r to set how many time each benchmark is run (ex: -r 5)
+# Pass --dev or -d to run tests in DEV and COMPAT_WARNINGS mode (ex: --dev)
+
+BENCHMARK_FILES = "bin/run-*-benchmark.lua"
+PROJECT_JSON = "tests.project.json"
+OUTPUT_PATTERN = r"(.+) x ([\d\.]+) ([/\w]+) ±([\d\.]+)\% \((\d+) runs sampled\)"
+
+import os
+import sys
+import re
+import subprocess
+import glob
+import svg
+os.system('color') # Colored output support
+
+# Profiler output node
+class Node(svg.Node):
+ def __init__(self):
+ svg.Node.__init__(self)
+ self.function = ""
+ self.source = ""
+ self.line = 0
+ self.ticks = 0
+
+ def text(self):
+ return self.function
+
+ def title(self):
+ if self.line > 0:
+ return "{}\n{}:{}".format(self.function, self.source, self.line)
+ else:
+ return self.function
+
+ def details(self, root):
+ return "Function: {} [{}:{}] ({:,} usec, {:.1%}); self: {:,} usec".format(self.function, self.source, self.line, self.width, self.width / root.width, self.ticks)
+
+
+# Default parameters
+parameters = {
+ "directory": "bin/benchmarks",
+ "runs": 3,
+ "dev": "",
+}
+
+# Parse command line arguments
+argNum = 1
+while argNum < len(sys.argv):
+ arg = sys.argv[argNum]
+ if arg == "-o" or arg == "--output":
+ value = sys.argv[argNum+1]
+ if value[0:1] != "-":
+ parameters['directory'] = value
+ else:
+ print(f"Error: Argument for {arg} is missing, please specify an output directory")
+ exit(1)
+
+ argNum += 2
+ elif arg == "-r" or arg == "--runs":
+ value = sys.argv[argNum+1]
+ if value[0:1] != "-":
+ parameters['runs'] = int(value)
+ else:
+ print(f"Error: Argument for {arg} is missing, please specify a number of runs")
+ exit(1)
+
+ argNum += 2
+ elif arg == "-d" or arg == "--dev":
+ parameters['dev'] = " --lua.globals=__DEV__=true --lua.globals=__COMPAT_WARNINGS__=true"
+
+ argNum += 1
+ elif arg[0:1] == "-":
+ print(f"Error: Unsupported flag {arg}")
+ exit(1)
+ else:
+ argNum += 1
+
+# Gather the path information
+branch = subprocess.getoutput("git symbolic-ref --short HEAD").strip().replace("/", "-")
+prefix=f"{parameters['directory']}/{branch}"
+logPath = f"{prefix}-benchmark.log"
+
+# Create the results directory
+if not os.path.exists(parameters['directory']):
+ os.makedirs(parameters['directory'])
+
+logFile = open(logPath, mode="w", encoding="utf-8")
+logFile.write("")
+logFile.flush()
+
+# Run each benchmark file
+for test in glob.iglob(BENCHMARK_FILES):
+ testName = test[8:-14]
+ print(f"\033[94mRunning {testName}...\033[0m", flush=True) # Colored output since benchmarks can be noisy and this helps readability
+
+ logFile.write(f"TEST: {testName.replace('-', ' ')}\n")
+ logFile.flush()
+ for i in range(1, parameters['runs']+1):
+ print(f" Run {i}", flush=True)
+ runResults = subprocess.Popen(
+ f"robloxdev-cli run --load.model {PROJECT_JSON} --run {test} --headlessRenderer 1 --fastFlags.overrides \"EnableDelayedTaskMethods=true\" \"FIntScriptProfilerFrequency=1000000\" \"DebugScriptProfilerEnabled=true\" \"EnableLoadModule=true\" --fastFlags.allOnLuau" + parameters['dev'],
+ encoding="utf-8", stdout=logFile,
+ )
+ runResults.wait()
+ logFile.flush()
+
+ # Generate flamegraph from last run data
+ flameFile=f"{prefix}-{testName.replace('/', '-')}-profile.svg"
+
+ dump = open("profile.out").readlines()
+ root = Node()
+
+ for l in dump:
+ ticks, stack = l.strip().split(" ", 1)
+ node = root
+
+ for f in reversed(stack.split(";")):
+ source, function, line = f.split(",")
+
+ child = node.child(f)
+ child.function = function
+ child.source = source
+ child.line = int(line) if len(line) > 0 else 0
+
+ node = child
+
+ node.ticks += int(ticks)
+
+ svg.layout(root, lambda n: n.ticks)
+ svg.display(open(flameFile, mode="w"), root, "Flame Graph", "hot", flip = True)
+
+ print(f"Flamegraph results written to {flameFile}")
+ if os.path.exists("profile.out"):
+ os.remove("profile.out")
+
+logFile.flush()
+logFile.close()
+
+# Process the benchmark data into a csv
+results = {}
+
+testName = ""
+for line in open(logPath, mode="r", encoding="utf-8").readlines():
+ newTestMatch = re.match(r"TEST: (.+)", line)
+ if newTestMatch:
+ testName = newTestMatch.group(1)
+ results[testName] = {}
+ else:
+ metricMatch = re.match(OUTPUT_PATTERN, line)
+ if metricMatch:
+ metric = metricMatch.group(1)
+ value = metricMatch.group(2)
+ unit = metricMatch.group(3)
+ deviation = metricMatch.group(4)
+ samples = metricMatch.group(5)
+
+ testResult = results.get(testName)
+ if not testResult:
+ testResult = {}
+ results[testName] = testResult
+
+ metricResult = testResult.get(metric)
+ if not metricResult:
+ metricResult = {
+ "count": 0,
+ "valueSum": 0,
+ "unit": unit,
+ "deviationSum": 0,
+ "samples": 0,
+ }
+ testResult[metric] = metricResult
+
+ metricResult['count'] += 1
+ metricResult['valueSum'] += float(value)
+ metricResult['unit'] = unit
+ metricResult['deviationSum'] += float(deviation)
+ metricResult['samples'] += int(samples)
+
+# Build the csv file from the result data
+outputFile = open(f"{prefix}-benchmark.csv", mode="w", encoding="utf-8")
+outputFile.write("Test,Metric,Value,Unit,Deviation,Samples")
+outputFile.write('\n')
+for testName, testResults in results.items():
+ for metric, metricResult in testResults.items():
+ outputFile.write("{test},\"{metric}\",{value:.5f},{unit},\"±{deviation:.2f}%\",{samples}".format(
+ test=testName,
+ metric=metric,
+ value=metricResult['valueSum']/metricResult['count'],
+ unit=metricResult['unit'],
+ deviation=metricResult['deviationSum']/metricResult['count'],
+ samples=metricResult['samples'],
+ ))
+ outputFile.write('\n')
+
+os.remove(logPath)
+outputFile.close()
+print(f"Benchmark results written to {prefix}-benchmark.csv")
diff --git a/bin/run-deep-tree-benchmark.lua b/bin/run-deep-tree-benchmark.lua
new file mode 100644
index 00000000..6e66de44
--- /dev/null
+++ b/bin/run-deep-tree-benchmark.lua
@@ -0,0 +1,15 @@
+local Packages = script.Parent.RoactAlignment
+local RotrieverWorkspace = Packages._Workspace
+
+local Roact = require(RotrieverWorkspace.React.React)
+local ReactRoblox = require(RotrieverWorkspace.ReactRoblox.ReactRoblox)
+
+local config = {}
+if _G.minSamples ~= nil then
+ config.sampleCount = tonumber(_G.minSamples)
+end
+
+require(RotrieverWorkspace.React.Dev.PerformanceBenchmarks).deepTreeBenchmark(
+ Roact,
+ ReactRoblox
+)(config)
diff --git a/bin/run-first-render-benchmark.lua b/bin/run-first-render-benchmark.lua
new file mode 100644
index 00000000..8e776e6b
--- /dev/null
+++ b/bin/run-first-render-benchmark.lua
@@ -0,0 +1,17 @@
+local Packages = script.Parent.RoactAlignment
+local RotrieverWorkspace = Packages._Workspace
+
+local Roact = require(RotrieverWorkspace.React.React)
+local ReactRoblox = require(RotrieverWorkspace.ReactRoblox.ReactRoblox)
+local Scheduler = require(RotrieverWorkspace.Scheduler.Scheduler)
+local firstRenderBenchmark =
+ require(RotrieverWorkspace.React.Dev.PerformanceBenchmarks).firstRenderBenchmark
+
+local config = {
+ minSamples = 200,
+}
+if _G.minSamples ~= nil then
+ config.minSamples = tonumber(_G.minSamples)
+end
+
+firstRenderBenchmark(Roact, ReactRoblox, Scheduler)(config)
diff --git a/bin/run-frame-rate-benchmark.lua b/bin/run-frame-rate-benchmark.lua
new file mode 100644
index 00000000..91ef8159
--- /dev/null
+++ b/bin/run-frame-rate-benchmark.lua
@@ -0,0 +1,17 @@
+local Packages = script.Parent.RoactAlignment
+local RotrieverWorkspace = Packages._Workspace
+
+local Roact = require(RotrieverWorkspace.React.React)
+local ReactRoblox = require(RotrieverWorkspace.ReactRoblox.ReactRoblox)
+local Scheduler = require(RotrieverWorkspace.Scheduler.Scheduler)
+local frameRateBenchmark =
+ require(RotrieverWorkspace.React.Dev.PerformanceBenchmarks).frameRateBenchmark
+
+local config = {
+ minSamples = 600,
+}
+if _G.minSamples ~= nil then
+ config.minSamples = tonumber(_G.minSamples)
+end
+
+frameRateBenchmark(Roact, ReactRoblox, Scheduler)(config)
diff --git a/bin/run-sierpinski-triangle-benchmark.lua b/bin/run-sierpinski-triangle-benchmark.lua
new file mode 100644
index 00000000..958e8540
--- /dev/null
+++ b/bin/run-sierpinski-triangle-benchmark.lua
@@ -0,0 +1,16 @@
+local Packages = script.Parent.RoactAlignment
+local RotrieverWorkspace = Packages._Workspace
+
+local Roact = require(RotrieverWorkspace.React.React)
+local ReactRoblox = require(RotrieverWorkspace.ReactRoblox.ReactRoblox)
+local sierpinskiTriangleBenchmark = require(
+ RotrieverWorkspace.React.Dev.PerformanceBenchmarks
+).sierpinskiTriangleBenchmark
+
+local config = {}
+if _G.minSamples ~= nil then
+ config.sampleCount = tonumber(_G.minSamples)
+end
+
+sierpinskiTriangleBenchmark(Roact, ReactRoblox)(config)
+wait(1)
diff --git a/bin/run-wide-tree-benchmark.lua b/bin/run-wide-tree-benchmark.lua
new file mode 100644
index 00000000..ce337952
--- /dev/null
+++ b/bin/run-wide-tree-benchmark.lua
@@ -0,0 +1,14 @@
+local Packages = script.Parent.RoactAlignment
+local RotrieverWorkspace = Packages._Workspace
+
+local Roact = require(RotrieverWorkspace.React.React)
+local ReactRoblox = require(RotrieverWorkspace.ReactRoblox.ReactRoblox)
+local wideTreeBenchmark =
+ require(RotrieverWorkspace.React.Dev.PerformanceBenchmarks).wideTreeBenchmark
+
+local config = {}
+if _G.minSamples ~= nil then
+ config.sampleCount = tonumber(_G.minSamples)
+end
+
+wideTreeBenchmark(Roact, ReactRoblox)(config)
diff --git a/bin/run-with-cachegrind.sh b/bin/run-with-cachegrind.sh
new file mode 100755
index 00000000..2b687628
--- /dev/null
+++ b/bin/run-with-cachegrind.sh
@@ -0,0 +1,126 @@
+#!/bin/bash
+
+# Tips for getting consistent perf benchmark results on inconsistent hardware
+# found here https://pythonspeed.com/articles/consistent-benchmarking-in-ci/
+
+set -euo pipefail
+IFS=$'\n\t'
+
+declare -A event_map
+event_map[Ir]="TotalInstructionsExecuted,executions\n"
+event_map[I1mr]="L1_InstrReadCacheMisses,misses/op\n"
+event_map[ILmr]="LL_InstrReadCacheMisses,misses/op\n"
+event_map[Dr]="TotalMemoryReads,reads\n"
+event_map[D1mr]="L1_DataReadCacheMisses,misses/op\n"
+event_map[DLmr]="LL_DataReadCacheMisses,misses/op\n"
+event_map[Dw]="TotalMemoryWrites,writes\n"
+event_map[D1mw]="L1_DataWriteCacheMisses,misses/op\n"
+event_map[DLmw]="LL_DataWriteCacheMisses,misses/op\n"
+event_map[Bc]="ConditionalBranchesExecuted,executions\n"
+event_map[Bcm]="ConditionalBranchMispredictions,mispredictions/op\n"
+event_map[Bi]="IndirectBranchesExecuted,executions\n"
+event_map[Bim]="IndirectBranchMispredictions,mispredictions/op\n"
+
+now_ms() {
+ echo -n $(date +%s%N | cut -b1-13)
+}
+
+# Run cachegrind on a given benchmark and echo the results.
+CLI_VERSION=$($1 version | tr -d '\n')
+ITERATION_COUNT=$4
+START_TIME=$(now_ms)
+
+# Get arch for disabling ALSR
+ARCH=$(uname -m | sed 's/ *$//g')
+
+# Run valgrind with virtual address randomization disabled, cachegrind enabled,
+# and cache sizes specified
+setarch \
+$ARCH \
+-R \
+valgrind \
+ --quiet \
+ --tool=cachegrind \
+ --I1=32768,8,64 \
+ --D1=32768,8,64 \
+ --LL=52428800,25,64 \
+ "$1" run \
+ --load.model model.rbxm \
+ --run "$2" \
+ --headlessRenderer 1 \
+ --lua.globals minSamples=$ITERATION_COUNT \
+ --lua.globals cachegrind=true \
+ --fastFlags.allOnLuau \
+>/dev/null
+
+TIME_ELAPSED=$(($(now_ms) - START_TIME))
+
+# Generate report using cg_annotate and extract the header and totals of the
+# recorded events valgrind was configured to record.
+CG_RESULTS=$(cg_annotate $(ls -t cachegrind.out.* | head -1))
+CG_HEADERS=$(grep -B2 'PROGRAM TOTALS$' <<< "$CG_RESULTS" | head -1 | sed -E 's/\s+/\n/g' | sed '/^$/d')
+CG_TOTALS=$(grep 'PROGRAM TOTALS$' <<< "$CG_RESULTS" | head -1 | grep -Po '[0-9,]+\s' | tr -d ', ')
+
+TOTALS_ARRAY=($CG_TOTALS)
+HEADERS_ARRAY=($CG_HEADERS)
+
+declare -A header_map
+for i in "${!TOTALS_ARRAY[@]}"; do
+ header_map[${HEADERS_ARRAY[$i]}]=$i
+done
+
+# Map the results to the format that the benchmark script expects.
+for i in "${!TOTALS_ARRAY[@]}"; do
+ TOTAL=${TOTALS_ARRAY[$i]}
+
+ # Labels and unit descriptions are packed together in the map.
+ EVENT_TUPLE=${event_map[${HEADERS_ARRAY[$i]}]}
+ IFS=$',' read -d '\n' -ra EVENT_VALUES < <(printf "%s" "$EVENT_TUPLE")
+ EVENT_NAME="${EVENT_VALUES[0]}"
+ UNIT="${EVENT_VALUES[1]}"
+
+ case ${HEADERS_ARRAY[$i]} in
+ I1mr | ILmr)
+ REF=${TOTALS_ARRAY[header_map["Ir"]]}
+ OPS_PER_SEC=$(awk "BEGIN {printf \"%.6f\", $TOTAL / $REF}")
+ ;;
+
+ D1mr | DLmr)
+ REF=${TOTALS_ARRAY[header_map["Dr"]]}
+ OPS_PER_SEC=$(awk "BEGIN {printf \"%.6f\", $TOTAL / $REF}")
+ ;;
+
+ D1mw | DLmw)
+ REF=${TOTALS_ARRAY[header_map["Dw"]]}
+ OPS_PER_SEC=$(awk "BEGIN {printf \"%.6f\", $TOTAL / $REF}")
+ ;;
+
+ Bcm)
+ REF=${TOTALS_ARRAY[header_map["Bc"]]}
+ OPS_PER_SEC=$(awk "BEGIN {printf \"%.6f\", $TOTAL / $REF}")
+ ;;
+
+ Bim)
+ REF=${TOTALS_ARRAY[header_map["Bi"]]}
+ OPS_PER_SEC=$(awk "BEGIN {printf \"%.6f\", $TOTAL / $REF}")
+ ;;
+
+ *)
+ OPS_PER_SEC=$(awk "BEGIN {printf \"%.6f\", $TOTAL}")
+ ;;
+ esac
+
+ STD_DEV="0%"
+ RUNS="1"
+
+ if [[ $OPS_PER_SEC =~ ^[+-]?[0-9]*$ ]]
+ then # $OPS_PER_SEC is integer
+ printf "%s#%s x %.0f %s ±%s (%d runs sampled)(roblox-cli version %s)\n" \
+ "$3" "$EVENT_NAME" "$OPS_PER_SEC" "$UNIT" "$STD_DEV" "$RUNS" "$CLI_VERSION"
+ else # $OPS_PER_SEC is float
+ printf "%s#%s x %.10f %s ±%s (%d runs sampled)(roblox-cli version %s)\n" \
+ "$3" "$EVENT_NAME" "$OPS_PER_SEC" "$UNIT" "$STD_DEV" "$RUNS" "$CLI_VERSION"
+ fi
+
+done
+
diff --git a/bin/spec.lua b/bin/spec.lua
index be57a167..4de000a3 100644
--- a/bin/spec.lua
+++ b/bin/spec.lua
@@ -1,32 +1,46 @@
-local Packages = script.Parent.RoactAlignment
-local ProcessService = game:GetService("ProcessService")
+--[[
+ * Copyright (c) Roblox Corporation. All rights reserved.
+ * Licensed under the MIT License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/MIT
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+]]
+local Workspace = script.Parent.RoactAlignment._Workspace
+local runCLI = require(Workspace.TestRunner.Dev.Jest).runCLI
-local RotrieverWorkspace = Packages._Workspace
-
--- ROBLOX FIXME: What's the more reasonable way of accessing this? Are all dev
--- dependencies hoisted to the top level in addition to existing as their
--- relevant interdependency links?
-local JestRoblox = require(RotrieverWorkspace.React.Dev.JestRoblox)
-local RobloxJest = require(RotrieverWorkspace.React.Dev.RobloxJest)
-
--- ROBLOX deviation: upstream mocks both of these via
--- scripts/setupHostConfigs.js, but this testing entry-point is the closest
--- equivalent we have
-RobloxJest.mock(RotrieverWorkspace.Scheduler.Scheduler, function()
- return require(RotrieverWorkspace.Scheduler.Scheduler.unstable_mock)
+local processServiceExists, ProcessService = pcall(function()
+ return game:GetService("ProcessService")
end)
--- Run all tests, collect results, and report to stdout.
-local result = JestRoblox.TestBootstrap:run(
- { RotrieverWorkspace },
- JestRoblox.Reporters.TextReporterQuiet,
- { extraEnvironment = RobloxJest.testEnv }
-)
+local status, result = runCLI(Workspace, {
+ verbose = if _G.verbose == "true" then true else nil,
+ ci = _G.CI == "true",
+ updateSnapshot = _G.UPDATESNAPSHOT == "true",
+}, { Workspace }):awaitStatus()
-if result.failureCount == 0 and #result.errors == 0 then
- ProcessService:ExitAsync(0)
+if status == "Rejected" then
+ print(result)
end
-ProcessService:ExitAsync(1)
+if
+ status == "Resolved"
+ and result.results.numFailedTestSuites == 0
+ and result.results.numFailedTests == 0
+then
+ if processServiceExists then
+ ProcessService:ExitAsync(0)
+ end
+end
+
+if processServiceExists then
+ ProcessService:ExitAsync(1)
+end
return nil
diff --git a/bin/svg.py b/bin/svg.py
new file mode 100644
index 00000000..e204352d
--- /dev/null
+++ b/bin/svg.py
@@ -0,0 +1,480 @@
+class Node:
+ def __init__(self):
+ self.name = ""
+ self.children = {}
+ # computed
+ self.depth = 0
+ self.width = 0
+ self.offset = 0
+
+ def child(self, name):
+ node = self.children.get(name)
+ if not node:
+ node = self.__class__()
+ node.name = name
+ self.children[name] = node
+ return node
+
+ def subtree(self):
+ result = [self]
+ offset = 0
+
+ while offset < len(result):
+ p = result[offset]
+ offset += 1
+ for c in p.children.values():
+ result.append(c)
+
+ return result
+
+def escape(s):
+ return s.replace("&", "&").replace("<", "<").replace(">", ">")
+
+def layout(root, widthcb):
+ for n in reversed(root.subtree()):
+ # propagate width to the parent
+ n.width = widthcb(n)
+ for c in n.children.values():
+ n.width += c.width
+
+ # compute offset from parent for every child in width order (layout order)
+ offset = 0
+ for c in sorted(n.children.values(), key = lambda x: x.width, reverse = True):
+ c.offset = offset
+ offset += c.width
+
+ for n in root.subtree():
+ for c in n.children.values():
+ c.depth = n.depth + 1
+ c.offset += n.offset
+
+# svg template (stolen from framegraph.pl)
+template = r"""
+
+
+
+
+
+
+
+
+
+
+
+
+$title
+Reset Zoom
+Search
+ic
+
+
+
+"""
+
+def namehash(s):
+ # FNV-1a
+ hval = 0x811c9dc5
+ for ch in s:
+ hval = hval ^ ord(ch)
+ hval = hval * 0x01000193
+ hval = hval % (2 ** 32)
+ return (hval % 31337) / 31337.0
+
+def display(file, root, title, colors, flip = False):
+ if colors == "cold":
+ gradient_start = "#eef2ee"
+ gradient_end = "#e0ffe0"
+ else:
+ gradient_start = "#eeeeee"
+ gradient_end = "#eeeeb0"
+
+ maxdepth = 0
+ for n in root.subtree():
+ maxdepth = max(maxdepth, n.depth)
+
+ svgheight = maxdepth * 16 + 3 * 16 + 2 * 16
+
+ file.write(template
+ .replace("$title", title)
+ .replace("$gradient-start", gradient_start)
+ .replace("$gradient-end", gradient_end)
+ .replace("$height", str(svgheight))
+ .replace("$status", str((svgheight - 16 + 3 if flip else 3 * 16 - 3)))
+ .replace("$flip", str(int(flip)))
+ )
+
+ framewidth = 1200 - 20
+
+ def pixels(x):
+ return float(x) / root.width * framewidth if root.width > 0 else 0
+
+ for n in root.subtree():
+ if pixels(n.width) < 0.1:
+ continue
+
+ x = 10 + pixels(n.offset)
+ y = (maxdepth - 1 - n.depth if flip else n.depth) * 16 + 3 * 16
+ width = pixels(n.width)
+ height = 15
+
+ if colors == "cold":
+ fillr = 0
+ fillg = int(190 + 50 * namehash(n.name))
+ fillb = int(210 * namehash(n.name[::-1]))
+ else:
+ fillr = int(205 + 50 * namehash(n.name))
+ fillg = int(230 * namehash(n.name[::-1]))
+ fillb = int(55 * namehash(n.name[::-2]))
+
+ fill = "rgb({},{},{})".format(fillr, fillg, fillb)
+ chars = width / (12 * 0.59)
+
+ text = n.text()
+
+ if chars >= 3:
+ if chars < len(text):
+ text = text[:int(chars-2)] + ".."
+ else:
+ text = ""
+
+ file.writelines([
+ "",
+ "{} ".format(escape(n.title())),
+ "{} ".format(escape(n.details(root))),
+ " ".format(x, y, width, height, fill),
+ "{} ".format(x + 3, y + 10.5, escape(text)),
+ "{} ".format(escape(n.text())),
+ " ",
+ ])
+
+ file.write(" \n \n")
diff --git a/bin/testing.sh b/bin/testing.sh
new file mode 100755
index 00000000..3fbc1e19
--- /dev/null
+++ b/bin/testing.sh
@@ -0,0 +1,98 @@
+#!/bin/bash
+set -e
+
+<&2
+ exit 1
+ fi
+ ;;
+ -d|--deferred)
+ DEFERRED=0
+ shift
+ ;;
+ -*|--*=)
+ echo "Error: Unsupported flag $1" >&2
+ exit 1
+ ;;
+ *) # Preserve positional arguments
+ PARAMS="$PARAMS $1"
+ shift
+ ;;
+ esac
+done
+# Set positional arguments in their proper place
+eval set -- "$PARAMS"
+
+# Perform requested action
+
+if [[ $SNAPSHOT ]]; then
+ echo "Generating snapshots..."
+ # Figure out how to enable PROFILING global in jestSetup once we upgrade to jest 28
+ # roblox-cli run --load.model tests.project.json --run bin/spec.lua --fastFlags.overrides EnableLoadModule=true --fastFlags.allOnLuau --lua.globals=__DEV__=true --lua.globals=__COMPAT_WARNINGS__=true --lua.globals=__PROFILE__=true --lua.globals=UPDATESNAPSHOT="all" --load.asRobloxScript --fs.readwrite="$(pwd)"
+ roblox-cli run --load.model tests.project.json --run bin/spec.lua --fastFlags.overrides EnableLoadModule=true DebugDisableOptimizedBytecode=true --fastFlags.allOnLuau --lua.globals=__DEV__=true --lua.globals=__COMPAT_WARNINGS__=true --lua.globals=UPDATESNAPSHOT="all" --load.asRobloxScript --fs.readwrite="$(pwd)"
+ exit 0
+fi
+
+if [[ $ANALYZE ]]; then
+ echo "Analyzing..."
+ robloxdev-cli analyze tests.project.json
+ exit 0
+fi
+
+if [[ $PROFILE ]]; then
+ echo "Generating profiled benchmark '$PROFILE'..."
+ robloxdev-cli run --load.model tests.project.json --run $PROFILE --headlessRenderer 1 --fastFlags.overrides "EnableDelayedTaskMethods=true" "FIntScriptProfilerFrequency=1000000" "DebugScriptProfilerEnabled=true" "EnableLoadModule=true" "DebugDisableOptimizedBytecode=true" --fastFlags.allOnLuau
+ python ../game-engine/Client/Luau/tools/perfgraph.py profile.out > $PROFILE-profile.svg
+ rm profile.out
+ start $PROFILE-profile.svg
+ echo "Flamegraph opened successfully in default svg viewer application"
+ exit 0
+fi
+
+if [[ $DEFERRED ]]; then
+ echo "Running tests with deferred lua enabled..."
+ roblox-cli run --load.model tests.project.json --run bin/spec.lua --fastFlags.allOn \
+ --fastFlags.overrides EnableLoadModule=true DebugDisableOptimizedBytecode=true EnableSignalBehavior=true DebugForceDeferredSignalBehavior=true \
+ --fastFlags.overrides MaxDeferReentrancyDepth=40 \
+ --lua.globals=__DEV__=true --lua.globals=__COMPAT_WARNINGS__=true
+else
+ echo "Running tests..."
+ # Figure out how to enable PROFILING global in jestSetup once we upgrade to jest 28
+ #roblox-cli run --load.model tests.project.json --run bin/spec.lua --fastFlags.overrides EnableLoadModule=true --fastFlags.allOnLuau --lua.globals=__DEV__=true --lua.globals=__COMPAT_WARNINGS__=true --lua.globals=__PROFILE__=true
+ roblox-cli run --load.model tests.project.json --run bin/spec.lua --fastFlags.allOn \
+ --fastFlags.overrides MaxDeferReentrancyDepth=40 \
+ --lua.globals=__DEV__=true --lua.globals=__COMPAT_WARNINGS__=true
+fi
+
diff --git a/bin/upstream-tag.sh b/bin/upstream-tag.sh
index 8d904063..c8b2ac19 100644
--- a/bin/upstream-tag.sh
+++ b/bin/upstream-tag.sh
@@ -33,7 +33,7 @@ do
continue
fi
- if [[ `head -n 1 $file` == "-- upstream:"* ]]; then
+ if [[ `head -n 1 $file` == "-- ROBLOX upstream:"* ]]; then
echo "SKIP: $file already has 'upstream' comment"
continue
fi
@@ -51,7 +51,7 @@ do
pushd $REACT_PATH > /dev/null
COMMIT=`git log -- $targetFile | head -n 1 | sed "s/commit //g"`
REPO_PATH=`realpath --relative-to=$REACT_PATH $targetFile`
- PREFIX="-- upstream: https://github.com/facebook/react/blob/$COMMIT/$REPO_PATH"
+ PREFIX="-- ROBLOX upstream: https://github.com/facebook/react/blob/$COMMIT/$REPO_PATH"
if [[ "$COMMIT" == "" ]]; then
echo "SKIP: Could not find commit for $targetFile -> $file"
continue
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 00000000..6ed82589
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,7 @@
+ignore:
+ - '*spec.lua'
+ - '*__test*'
+ - '*jest*'
+ - '*mock*'
+ - '*Dev*'
+ - '*/_Index/*'
diff --git a/docs/align-files-guide.md b/docs/align-files-guide.md
new file mode 100644
index 00000000..6f077922
--- /dev/null
+++ b/docs/align-files-guide.md
@@ -0,0 +1,163 @@
+# Aligning files guide
+
+We are aligning files by going through them module by module. We keep track of them in a [Adapt roact-alignment implementation](https://github.com/Roblox/roact-alignment/milestone/1) milestone.
+
+## Prerequisites
+
+In this guide we assume that you have the following project cloned inside of the common directory:
+* js-to-lua [repo link](https://github.com/roblox/js-to-lua)
+ * checkout `main` branch
+* roact-alignment [repo link](https://github.com/roblox/roact-alignment)
+ * your working branch on top of `master` branch
+* react [repo link](https://github.com/facebook/react)
+ * checkout `v17.0.2` tag
+
+## Tools installation
+
+First follow the [README](https://github.com/Roblox/js-to-lua/blob/main/README.md) instructions to set up js-to-lua. The easiest way is to build the tool by running `npx nx build convert-js-to-lua` (from within `js-to-lua` directory)
+
+## Aligning process
+
+I will show you the process based on the `react-devtools-shared` module. All the commands shown below are to be executed from `js-to-lua` directory
+
+Depending on the size of the module and/or your preference you can either align all the files at once or do it file by file. In order to convert the whole module at once you can run the following command
+
+```shell
+dist/apps/convert-js-to-lua/index.js \
+ --input ../react/packages/react-devtools-shared/src/**/*.js \
+ --output ../roact-alignment/modules/ \
+ --rootDir ../react/packages \
+ --plugin=knownImports --plugin=jestGlobals \
+ --babelTransformConfig babel-flow-transform-react.config.json \
+ --babelConfig babel-flow.config.json
+```
+
+This will run the conversion on all the files in the `react-devtools-shared` which in our case is a bit too much as this module contains a lot of files that haven't been converted yet. That's why for this particular module I chose to go file by file:
+
+```shell
+dist/apps/convert-js-to-lua/index.js \
+ --input ../react/packages/react-devtools-shared/src/ \
+ --output ../roact-alignment/modules/ \
+ --rootDir ../react/packages \
+ --plugin=knownImports --plugin=jestGlobals \
+ --babelTransformConfig babel-flow-transform-react.config.json \
+ --babelConfig babel-flow.config.json
+```
+
+eg.
+
+```
+dist/apps/convert-js-to-lua/index.js \
+ --input ../react/packages/react-devtools-shared/src/utils.js \
+ --output ../roact-alignment/modules/ \
+ --rootDir ../react/packages \
+ --plugin=knownImports --plugin=jestGlobals \
+ --babelTransformConfig babel-flow-transform-react.config.json \
+ --babelConfig babel-flow.config.json
+```
+
+After we run the automatic conversion the file will contain a lot of changes. Please additionally run `stylua module` in the roact-alignment directory to make sure all the files are converted according to the StyLua configuration.
+
+Now our job is to capture the deviations that the original code has made and mark them with appropriate `ROBLOX deviation START` / `ROBLOX deviation END` comments.
+
+There are some special cases which fast-follow treats separately and you don't need to wrap them in a deviation comment:
+
+* upstream comment - with the command shown above there is a small issue with part of the upstream comment not being included. In this case we need to add the missing `packages/` part of the path.
+ 
+* Packages - js-to-lua can't automatically assign a proper path to `Packages` variable right now. However, fast-follow will treat conflicts resulting from this line separately so there is no need to wrap it with deviation comment.
+ 
+
+We've added a bunch of improvements in js-to-lua to the output code so be mindful that new types might be better left as is straight from the conversion. Also you might encounter a lot of diff when it comes to class conversion. I suggest leaving the class conversion structure as is and only mark deviation inside of the class's methods body.
+
+For example the following diff should not be a deviation anymore:
+
+
+And with class conversion the following example shows how js-to-lua handles class "methods" which are defined as arrow functions. The body of the function might still need to be marked with deviation blocks but the structure should most probably stay the same
+
+ 
+
+As for the actual deviations that need to be marked let's look at the following example. We can see that the call to `Boolean.toJSBoolean` is unnecessary in this case so we can comment-out that line, wrap it with `-- ROBLOX deviation START:` and `-- ROBLOX deviation END` comments and follow the commented out line with the proper implementation.
+
+
+
+
+In order to make that process a little bit easier we've created a set of VSCode snippets that you can use - [link](#snippets) to snippets section.
+
+## Snippets
+
+You can configure your snippets by invoking `Snippets: Configure User Snippets` command from VSCode's command pallette and choosing `.lua` extension. Below you can find all of the snippets we've used.
+
+In order to use the snippets you should configure a hot key (keyboard shortcut) for `Snippets: Insert Snippet` action. Then you can just select a piece of code that you need to mark with deviation, press the hot key and choose the appropriate snippet:
+
+* `deviation-block` - will comment out the currently selected code, wrap it with deviation START/END comments and insert a duplicated version of the same code for you to tweak
+* `deviation-block-skipped` - will do the same as `deviation-block` but won't insert a duplicated code as it's meant for blocks of code that should be skipped
+* `deviation-block-add` - will wrap the currently selected block of code with deviation START/END comments and allow you to put an additional code inside. It's intended for some additional blocks of code that were not included in the original conversion. We use a convention of `-- ROBLOX deviation START: add ` to easily distinguish added and changes sections
+* `deviation-start` and `deviation-end` - will only insert a proper deviation start/end comment. We use those to avoid typos.
+
+There are couple more utility snippets for special cases that were occurring more often:
+* `deviation-block-type` - it will wrap a block with deviation comments, comment out the block and insert a duplicate but change `local` for `type`. It's useful when js-to-lua imported locals from a module when it should actually import types
+* `deviation-line` and `deviation-line-skipped` are similar to their block equivalents but will use the current line instead of selected block of code
+
+```json
+{
+ "Deviation block - start": {
+ "prefix": "deviation-start",
+ "body": "-- ROBLOX deviation START: ${0:description}",
+ "description": "Roblox deviation start marker"
+ },
+ "Deviation block - end": {
+ "prefix": "deviation-end",
+ "body": "-- ROBLOX deviation END",
+ "description": "Roblox deviation end marker"
+ },
+ "Deviation block": {
+ "prefix": "deviation-block",
+ "body": [
+ "-- ROBLOX deviation START: ${0:changed because}",
+ "-- ${TM_SELECTED_TEXT/\n/\n-- /g}",
+ "$TM_SELECTED_TEXT",
+ "-- ROBLOX deviation END"
+ ],
+ "description": "Roblox deviation end marker",
+ },
+ "Deviation block - type": {
+ "prefix": "deviation-block-type",
+ "body": [
+ "-- ROBLOX deviation START: ${0:import as type instead of local}",
+ "-- ${TM_SELECTED_TEXT/\n/\n-- /g}",
+ "${TM_SELECTED_TEXT/^local /type /gm}",
+ "-- ROBLOX deviation END"
+ ],
+ "description": "Roblox deviation end marker",
+ },
+ "Deviation block - add": {
+ "prefix": "deviation-block-add",
+ "body": [
+ "-- ROBLOX deviation START: ${0:add some code}",
+ "${TM_SELECTED_TEXT}",
+ "${1}",
+ "-- ROBLOX deviation END"
+ ],
+ "description": "Roblox deviation end marker",
+ },
+ "Deviation block - skip": {
+ "prefix": "deviation-block-skipped",
+ "body": [
+ "-- ROBLOX deviation START: ${0:changed because}",
+ "-- ${TM_SELECTED_TEXT/\n/\n-- /g}",
+ "-- ROBLOX deviation END"
+ ],
+ "description": "Roblox deviation end marker",
+ },
+ "Deviation line": {
+ "prefix": "deviation-line",
+ "body": ["-- ROBLOX deviation START: ${0:changed because}", "-- $TM_CURRENT_LINE", "$TM_CURRENT_LINE", "-- ROBLOX deviation END"],
+ "description": "Roblox deviation end marker"
+ },
+ "Deviation line - skipped": {
+ "prefix": "deviation-line-skipped",
+ "body": ["-- ROBLOX deviation START: ${0:changed because}", "-- $TM_CURRENT_LINE", "-- ROBLOX deviation END"],
+ "description": "Roblox deviation end marker"
+ }
+}
+```
diff --git a/docs/api-reference/additional-libraries.md b/docs/api-reference/additional-libraries.md
new file mode 100644
index 00000000..a983ad6a
--- /dev/null
+++ b/docs/api-reference/additional-libraries.md
@@ -0,0 +1,10 @@
+# Additional Libraries
+
+## ReactIs
+*Under construction 🔨*
+
+## ReactTestRender
+*Under construction 🔨*
+
+## ReactDevToolsExtensions
+*Under construction 🔨*
diff --git a/docs/api-reference/react-roblox.md b/docs/api-reference/react-roblox.md
new file mode 100644
index 00000000..afeef06e
--- /dev/null
+++ b/docs/api-reference/react-roblox.md
@@ -0,0 +1,59 @@
+# ReactRoblox
+
+The ReactRoblox package is the entry point for any Roblox-opinionated logic. It can be thought of as the equivalent of the ReactDOM package in React JS.
+
+However, rather than fully implementing all of the features of ReactDOM, it leaves most deprecated APIs out entirely, implementing only the modern and forward-looking portions of the API.
+
+Notable absences are:
+
+* `render` - Use `createRoot` (or, depending on the circumstances, `createBlockingRoot` or `createLegacyRoot`) instead
+* `unmountComponentAtNode` - Use the `unmount` method on the root object returned from `createRoot` instead
+* `findDOMNode` - Use [refs](https://reactjs.org/docs/refs-and-the-dom.html) instead
+
+## ReactRoblox.createRoot
+Adopted as part of the Concurrent Mode API. Refer to [`ReactDOM.createRoot` documentation](https://reactjs.org/docs/concurrent-mode-reference.html#createroot).
+
+## ReactRoblox.createBlockingRoot
+Adopted as part of the Concurrent Mode API. "Blocking" roots represent a transitional step between synchronous rendering and full concurrent rendering. Refer to [`ReactDOM.createBlockingRoot` documentation](https://reactjs.org/docs/concurrent-mode-adoption.html#migration-step-blocking-mode).
+
+## ReactRoblox.createLegacyRoot
+Adopted as part of the Concurrent Mode API. "Legacy" roots are essentially equivalent to pre-concurrent React behavior. Refer to [`ReactDOM.createLegacyRoot` documentation](https://reactjs.org/docs/concurrent-mode-adoption.html#migration-step-blocking-mode).
+
+### Deviations
+React documentation provides [a handy chart](https://reactjs.org/docs/concurrent-mode-adoption.html#feature-comparison) that explains the feature differences between the roots. This table lists three deprecated features under legacy roots only:
+
+* [String Refs](https://reactjs.org/docs/refs-and-the-dom.html#legacy-api-string-refs)
+* [Legacy Context](https://reactjs.org/docs/legacy-context.html)
+* [findDOMNode](https://reactjs.org/docs/strict-mode.html#warning-about-deprecated-finddomnode-usage)
+
+**These features were never implemented in legacy Roact, and have not been ported.** Instead, creating a Legacy Mode root simply tells Roact to render synchronously. This behavior is most similar to legacy Roact or pre-Concurrent-Mode React JS.
+
+Generally speaking, `ReactRoblox.createRoot` should be used for all new work, while the Blocking and Legacy versions can be used for upgrading existing Roact logic in the event that it does not yet meet the requirements for Concurrent Mode.
+
+## ReactRoblox.createPortal
+Refer to [`ReactDOM.createPortal` documentation](https://reactjs.org/docs/portals.html).
+
+
+## ReactRoblox.act
+Used for testing code with scheduling logic mocked. This ensures that tests behave consistently and don't batch work unpredictably across frames. `act` executes a function and then plays forward the mock scheduler after it's done, and should be used to wrap any interactions that trigger updates to a roact UI.
+
+More info on using `act` can be found [in the migration guide](../migrating-from-1x/adopt-new-features.md#reactrobloxact).
+
+Refer to [ReactDOM's `act` testing documentation](https://reactjs.org/docs/test-utils.html#act) for more high-level guidance.
+
+### Deviations
+In production, `ReactRoblox.act` will not be available. Set the global value `_G.__ROACT_17_INLINE_ACT__` to `true` in order to enable this behavior in tests.
+
+!!! info
+ This behavior is still a work in progress, and there may be changes to how Roact detects whether or not it should provide the `act` function. For now, use the global `__ROACT_17_INLINE_ACT__` to enable it for tests.
+
+## ReactRoblox.Event
+*Roact-only*
+
+Re-exports [`React.Event`](react.md#reactevent). This is only exposed for backwards compatibility; please prefer to use `React.Event`.
+
+## ReactRoblox.Change
+*Roact-only*
+
+Re-exports [`React.Change`](react.md#reactchange). This is only exposed for backwards compatibility; please prefer to use `React.Change`.
+
diff --git a/docs/api-reference/react.md b/docs/api-reference/react.md
new file mode 100644
index 00000000..f3253baf
--- /dev/null
+++ b/docs/api-reference/react.md
@@ -0,0 +1,868 @@
+# React
+
+!!! info
+ This page is intended to provide *brief* descriptions, examples, and notes about deviations in behavior or API. For complete feature documentation of upstream-aligned features, follow the `View React Docs` link and consult the React JS docs.
+
+The `React` package is the entry point for most React logic and behavior. Most of its API members align directly to their equivalents in [React JS](https://reactjs.org/docs/react-api.html). Some API members have slightly different behavior, different guidance around their usage, or slightly different function signatures to better accommodate Luau functionality and idioms.
+
+### API Deviations
+
+#### Not Supported
+The following API members are notable absences relative to React JS 17.0.1:
+
+* `React.createFactory` - Considered legacy and will likely not be included
+* `React.useDebugValue` - Not yet implemented
+
+Additionally, the following APIs are implemented, but have blocking issues that prevent them from being fully supported or usable:
+
+* [`React.lazy`](#reactlazy)
+* [`React.Suspense`](#reactsuspense)
+
+#### Undocumented
+The `React` packages includes some features that are undocumented in React JS 17.0.1, but are present in its implementation. The following API members may be less stable than the features that are fully documented upstream:
+
+* [`React.createMutableSource`](#reactcreatemutablesource)
+* [`React.useMutableSource`](#reactusemutablesource)
+
+#### Unique to Roblox
+`React` also exports a small collection of [Roact-exclusive APIs](#roblox-and-luau) which have no upstream equivalent. These are designed to facilitate better integration with the Luau language and the Roblox engine functionality.
+
+* [`React.None`](#reactnone)
+* [`React.Event`](#reactevent)
+* [`React.Change`](#reactchange)
+* [`React.Tag`](#reacttag)
+* [`React.useBinding`](#reactusebinding)
+* [`React.createBinding`](#reactcreatebinding)
+* [`React.joinBindings`](#reactjoinbindings)
+
+## React.Component
+
+
+Luau does not have ES6's class semantics, so class components work slightly differently from React JS in the following ways:
+
+* Use `Component:extend` in place of ES6 class semantics
+* Implement an `init` method on components instead of a constructor
+
+Roact 17 also includes the following deviations:
+
+* Instead of initializing component state by assigning a value to `self.state` in the `init` method, use `setState` as you would elsewhere (this is inherited from Legacy Roact for compatibility and consistency)
+* Error boundaries are not fully supported in Roact 17 at this time due to a limitation in recursive `pcall` depth
+
+```lua
+local MyComponent = React.Component:extend("MyComponent")
+
+function MyComponent:init()
+ self:setState({ expanded = true })
+end
+
+function MyComponent:render()
+ return React.createElement("TextButton", {
+ Text = if expanded
+ then self.props.text
+ else "Click to Expand",
+ Size = if expanded
+ then UDim2.new(1, 0, 0, 200)
+ else UDim2.new(1, 0, 0, 30),
+ [React.Event.Activated] = function()
+ self:setState(function(expanded)
+ return { expanded = not expanded }
+ end)
+ end
+ })
+end
+```
+
+Check the [deviations guide](../deviations.md#class-components) for more detailed information.
+
+## React.PureComponent
+
+
+The same deviations to `React.Component` apply equivalently to `React.PureComponent`. Check the [deviations guide](../deviations.md#class-components) for more detailed information.
+
+## React.memo
+
+
+```lua
+local MyComponent = React.memo(function MyComponent(props)
+ --[[ render using props ]]
+end)
+```
+
+Guidance specified in the [React documentation](https://beta.reactjs.org/reference/react/memo#usage) applies for Roact as well. Use this only as a performance optimization, and only when relevant to the use case.
+
+## React.createElement
+
+
+While the `createElement` API is aligned to React JS, current tooling in Luau does not support an equivalent for JSX. When writing Roact code, you'll use `createElement` directly much more often than in React JS.
+
+```lua
+--[[
+React.createElement(
+ type,
+ [props],
+ [...children]
+)
+]]
+local element = React.createElement(
+ "Frame",
+ { Size = UDim2.fromScale(1, 1) },
+ React.createElement("TextLabel", { Text = "Child1" })
+ React.createElement("TextLabel", { Text = "Child2" })
+)
+```
+
+## React.cloneElement
+
+
+Clone an existing element, using its provided config values (props, `ref`, and `key`) as a starting point.
+
+```lua
+local onlyChild = React.Children.only(props.children)
+local cloned = React.cloneElement(onlyChild, { Text = "Cloned" })
+```
+
+## React.isValidElement
+
+
+Returns `true` if the provided value is a React element; otherwise, returns `false`.
+
+```lua
+if React.isValidElement(props.frameContents) then
+ return React.createElement("Frame", { Size = UDim2.fromScale(1, 1) }, props.frameContents)
+end
+```
+
+## React.Children
+
+
+!!! warning
+ This field can often be confused with a Legacy Roact API ca lled `Roact.Children`. In the legacy API, `Roact.Children` was a special opaque prop key that could be used to pass children through props tables. In Roact 17, the `children` string key is reserved and used instead. Learn more about how to migrate away from `Roact.Children` in the [migration guide](../migrating-from-1x/convert-legacy-conventions.md#reserved-keys).
+
+A collection of utilities for manipulating the children provided to a React component via `props.children` or `self.props.children`. The `children` property should always be used as an opaque data structure; manipulate it via `React.Children` utilities rather than accessing it directly.
+
+There are a few notable deviations:
+
+* React Children values with type "userdata" will be treated as nil in callbacks. This means that a `React.None` child passed to `React.Children.forEach` or `React.Children.map` will be treated the same as a nil value or boolean in the callbacks. `React.Children.count` will not include userdata children in the count.
+* The `context` argument for mapChildren is not passed to the callback. This is typically used to pass `this` in javascript, but does not have an equivalent in lua.
+* React Children utilities work with tables with string keys as well as arrays
+
+
+### React.Children.map
+
+
+Use `React.Children.map` to transform each member child of the `children` property.
+
+```lua
+local ListItem = require(script.Parent.ListItem)
+
+local function OrderedList(props)
+ return React.createElement(
+ "Frame",
+ { Size = UDim2.fromScale(1, 1) },
+ React.Children.map(props.children, function(child, i)
+ return React.createElement(ListItem, { layoutOrder = i }, child)
+ end)
+ )
+end
+```
+
+### React.Children.forEach
+
+
+Accepts a function that will be called with each member child of the `children` property. Similar to `React.Children.map`, but the provided callback does not return a new child.
+
+```lua
+local ListItem = require(script.Parent.ListItem)
+local Separator = require(script.Parent.Separator)
+
+local function SeparatorList(props)
+ local listItems = {}
+
+ React.Children.forEach(props.children, function(child, i)
+ table.insert(listItems, React.createElement(ListItem, { layoutOrder = i*2 }, child))
+ table.insert(listItems, React.createElement(Separator, { layoutOrder = i*2 + 1 }))
+ end)
+
+ return React.createElement(
+ "Frame",
+ { Size = UDim2.fromScale(1, 1) },
+ listItems
+ )
+end
+```
+
+### React.Children.count
+
+
+Returns the number of child members in the provided `children` object.
+
+```lua
+local function List(props)
+ print(string.format("Render list with %d children", React.Children.count(props.children)))
+
+ return React.createElement(
+ "Frame",
+ { Size = UDim2.fromScale(1, 1) },
+ props.children,
+ )
+end
+```
+
+### React.Children.only
+
+
+Asserts that the provided object is a single React element. If the provided object is an array or table of elements, this function will throw; otherwise, it returns the element that was passed into it.
+
+```lua
+local function Box(props)
+ local element = React.Children.only(props.children)
+
+ return React.createElement(
+ "Frame",
+ { Size = UDim2.fromOffset(100, 100) },
+ element,
+ )
+end
+```
+
+### React.Children.toArray
+
+
+Converts the opaque `children` prop into an array. The resulting value can then be manipulated with typical Lua array semantics or using utility methods that operate on array-like Lua tables.
+
+```lua
+local Array = require(Packages.Collections).Array
+
+local function FilteredList(props)
+ local children = React.Children.toArray(props.children)
+
+ return React.createElement(
+ "Frame",
+ { Size = UDim2.fromScale(1, 1) },
+ Array.filter(children, props.filterFn),
+ )
+end
+```
+
+## React.Fragment
+
+
+Renders multiple components using a special component called a `Fragment`. Fragments are useful for returning multiple elements from a single component without creating additional Instances in the DataModel.
+
+```lua
+function MyComponent:render()
+ return React.createElement(
+ React.Fragment,
+ nil,
+ React.createElement("TextLabel", { Text = "Some text." }),
+ React.createElement("TextButton", { Text = "Button" }),
+ )
+end
+```
+
+## React.createRef
+
+
+Creates a Ref object which can be used to access underlying Instances in the DataModel. Use this as an escape hatch when you need to interact directly with Roblox Instances.
+
+```lua
+local MyComponent = React.Component:extend("MyComponent")
+
+function MyComponent:init()
+ self.inputRef = React.createRef()
+end
+
+function MyComponent:render()
+ return React.createElement("TextBox", { ref = self.inputRef })
+end
+
+function MyComponent:componentDidMount()
+ self.inputRef.current:CaptureFocus()
+end
+```
+
+## React.forwardRef
+
+
+In some cases, you may want your component to accept a ref and forward it to a child, typically a Roblox host component.
+
+`React.forwardRef` accepts a render function that receives `props` (just like a function component) as well as a `ref` object, and can then assign its ref within the render callback.
+
+```lua
+local FancyButton = React.forwardRef(function(props, ref)
+ return React.createElement("TextButton", {
+ ref = ref,
+ Text = props.text,
+ [React.Event.Activated] = props.onActivated,
+ }, props.children)
+end)
+
+-- You can now get a ref directly to the TextButton Instance:
+local ref = React.createRef()
+React.createElement(FancyButton, { ref = ref, text = "Click me!" })
+```
+
+## React.createContext
+
+
+Creates a Context object. Context is a mechanism for passing information through many levels of the React component tree without needing to explicitly pass it down at each level.
+
+```lua
+local MyContext = React.createContext(defaultValue)
+```
+
+### Context.Provider
+
+
+The Context object returned from `createContext` includes a `Provider` component, which can be used to pass a context value into the component tree.
+
+```lua
+React.createElement(MyContext.Provider, { value = someValue })
+```
+
+### Context.Consumer
+
+
+The Context object returned from `createContext` includes a `Consumer` component, which can be used to retrieve a context value that was provided higher up in the tree.
+
+```lua
+React.createElement(MyContext.Consumer, nil, function(value)
+ -- render something based on the context value
+end)
+```
+
+## React.lazy
+
+
+!!! warning
+ Though `React.lazy`'s functionality is implemented, it may not be especially useful without complete support for [`React.Suspense`](#reactsuspense).
+
+!!! warning
+ The `lazy` utility is designed an ecosystem where loading modules can be expensive. **This is rarely a concern in the context of Luau projects.** Since `lazy` relies upon a dynamic call to `require`, tooling like type checking may not work correctly. Use `React.lazy` with caution and intent, and only if it has a measurable performance impact.
+
+Wraps a component in a lazy-loading container that waits until the component is used in a render before calling the function to load it.
+
+```lua
+local Component = React.lazy(function()
+ return require(script.Parent.Component)
+end)
+```
+
+
+## React.Suspense
+
+
+!!! warning
+ **While `React.Suspense` is technically implemented, it should be considered unusable as of version `17.0.1`.** This is due to a limitation in Luau around recursive `pcall` depth. Future updates to React will unravel the recursive traversal and enable these features.
+ Future version of React eliminate this recursive structure and should enable full use of `Suspense` and error boundaries.
+
+Renders a subtree of children that can yield. If the children suspend while rendering, the `fallback` field provided will be used until the children have resolved their render.
+
+```lua
+React.createElement(
+ React.Suspense,
+ {
+ fallback = React.createElement("TextLabel", {
+ Text = "Loading...",
+ })
+ },
+ React.createElement(SomeContents)
+)
+```
+
+## Hooks
+
+Hooks allow simple function components to introduce stateful behaviors without incurring the overhead or complexity of the full [Component](#reactcomponent) lifecycle.
+
+### React.useState
+
+
+`useState` is a React Hook that creates a state variable that can be used and updated by your component. Updating the state will cause the component to re-render.
+
+```lua
+local function ClickCounter(props)
+ local count, setCount = React.useState(0)
+
+ return React.createElement("TextButton", {
+ Text = string.format("Clicked %d times", count)
+ -- Clicking the button updates the count, which re-renders the component
+ [React.Event.Activated] = function()
+ setCount(count + 1)
+ end
+ })
+end
+```
+
+Note that Luau does not have syntactic sugar for destructuring like javascript:
+```js
+const [value, setValue] = useState(0);
+```
+However, it _does_ support multiple return values, so Roact 17 supports a very similar usage:
+```lua
+local value, setValue = useState(0)
+```
+
+### React.useEffect
+
+
+`useEffect` is a React Hook that causes a side effect to run after a component's render has completed, allowing synchronization with external systems.
+
+```lua
+local UserInputService = game:GetService("UserInputService")
+local LastInputTypeChanged = UserInputService.LastInputTypeChanged
+
+local function LastInputLabel(props)
+ local lastInput, setLastInput = React.useState(Enum.UserInputType.None)
+
+ React.useEffect(function()
+ -- Update `lastInput` state when the engine signal fires
+ local connection = LastInputTypeChanged:Connect(setLastInput)
+
+ return function()
+ connection:Disconnect()
+ end
+ end)
+
+ return React.createElement("TextLabel", {
+ Text = string.format("Last input: %s", lastInput)
+ })
+end
+```
+
+### React.useContext
+
+
+`useContext` is a React Hook that allows a component to read from and subscribe to context values provided from [`createContext`](#reactcreatecontext).
+
+```lua
+local ThemeContext = require(...)
+
+local function ThemedLabel(props)
+ local theme = React.useContext(ThemeContext)
+
+ return React.createElement("TextLabel", {
+ TextSize = theme.TextSize,
+ TextColor3 = theme.TextColor3,
+ -- ...
+ })
+end
+```
+
+### React.useReducer
+
+
+`useReducer` is a React Hook that introduces a reducer function to your component for managing complex state. See the [`Rodux` documentation](https://roblox.github.io/rodux/introduction/reducers/) for detailed examples of the reducer pattern in Lua.
+
+```lua
+local function reducer(state, action)
+ if action.type == "add" then
+ return {
+ value = state.value + action.value,
+ ops = state.ops + 1,
+ }
+ elseif action.type == "multiply" then
+ return {
+ value = state.value * action.value,
+ ops = state.ops + 1,
+ }
+ end
+end
+
+local function Calculator(props)
+ local state, dispatch = React.useReducer(reducer, { value = 1, ops = 0 })
+
+ return React.createElement(React.Fragment, nil,
+ React.createElement("TextLabel", {
+ Text = string.format("value: %d - ops: %d", state.value, state.ops)
+ })
+ React.createElement("TextButton", {
+ Text = "Add 4",
+ [React.Event.Activated] = function()
+ dispatch({ type = "add", value = 4 })
+ end
+ })
+ React.createElement("TextButton", {
+ Text = "Multiply by 3",
+ [React.Event.Activated] = function()
+ dispatch({ type = "multiply", value = 3 })
+ end
+ })
+ )
+end
+```
+
+Just like the `useState` hook, we use Luau's multiple returns to approximate JavaScript's destructuring. The following JS:
+```js
+const [state, dispatch] = useReducer(reducer, initialState);
+```
+However, it _does_ support multiple return values, so Roact 17 supports a very similar usage:
+```lua
+local state, dispatch = useReducer(reducer, initialState)
+```
+
+### React.useCallback
+
+
+`useCallback` is a React Hook that allows a component to cache a function definition between re-renders.
+
+```lua
+local ItemTile = require(...)
+local equipItem = require(...)
+
+local function EquippableItemTile(props)
+ local onClick = React.useCallback(function()
+ equipItem(props.itemId)
+ end, { props.itemId })
+
+ return React.createElement(ItemTile, {
+ text = props.itemName,
+ onTileClicked = onClick,
+ })
+end
+```
+
+### React.useMemo
+
+
+`useMemo` is a React Hook that caches a computed value between re-renders.
+
+```lua
+local function filterByCategory(todos, category)
+ -- ...
+ return filteredTodos
+end
+
+local function FilteredTodoList(props)
+ local filteredTodos = React.useMemo(function()
+ return filterByCategory(props.todos, props.selectedCategory)
+ end, { props.todos, props.selectedCategory })
+
+ return React.createElement(TodoList, {
+ items = filteredTodos,
+ })
+end
+```
+
+### React.useRef
+
+
+`useRef` is a React Hook that stores a value that's not needed for rendering. In other words, refs are arbitrary value containers that do no invoke re-renders when they change.
+
+Refs can be used for a variety of purposes, including assigning them to the `ref` field of an element to get a reference to the underlying Roblox `Instance`.
+
+```lua
+local function TextBoxWithButton(props)
+ local textBoxRef = React.useRef(nil)
+
+ return React.createElement(React.Fragment, nil,
+ React.createElement("TextBox", { ref = textBoxRef }),
+ React.createElement("TextButton", {
+ Text = "->",
+ [React.Event.Activated] = function()
+ textBoxRef.current:CaptureFocus()
+ end
+ }),
+ )
+end
+```
+
+### React.useImperativeHandle
+
+
+`useImperativeHandle` is a React Hook that provides a way to customize a handle that's exposed as a ref.
+
+This allows a ref to be defined around a narrower or more convenient interface rather than a direct reference to the underlying `Instance`, and can be used to expose a customized ref interface from a function or class component.
+
+```lua
+local ControllableScrollingFrame = React.forwardRef(function(props, ref)
+ local innerRef = React.useRef(nil)
+
+ React.useImperativeHandle(ref, function()
+ return {
+ scrollToTop = function()
+ innerRef.current.CanvasPosition = Vector2.new(0, 0)
+ end
+ }
+ end, {})
+
+ return React.createElement("ScrollingFrame", {
+ ref = innerRef
+ -- ...
+ })
+end)
+
+-- Another component will be able to access this interface via a ref:
+local function TodoListApp(props)
+ local scrollingFrameRef = React.useRef(nil)
+
+ return React.createElement(ControllableScrollingFrame,
+ {
+ ref = scrollingFrameRef
+ },
+ React.createElement(TodoList, {
+ items = props.todoItems,
+ }),
+ React.createElement("TextButton", {
+ Text = "Back to top",
+ [React.Event.Activated] = function()
+ scrollingFrameRef.current.scrollToTop()
+ end
+ })
+ )
+end
+```
+
+### React.useLayoutEffect
+
+
+`useLayoutEffect` is an alternative version of `useEffect` that fires after the component runs its render function, but before the Roblox `Instance` hierarchy is reconciled with the new properties.
+
+!!! caution
+ Use this hook sparingly. While `useEffect` runs asynchronously after a render operation is completed, `useLayoutEffect` runs _during_ an update in between rendering and reconciliation: this can introduce performance issues if used carelessly.
+
+ In React JS, `useLayoutEffect` is most often used to measure and position elements before the browser repaints them. It's not yet clear how this use case translates to roblox usage, and this hook should be used with caution until performance is investigated in detail and best practices emerge.
+
+### React.useBinding
+
+
+A hook introduced in Roact that creates a `Binding` object. Bindings are isolated containers that automatically update. Creates and returns a binding and its associated updater function as multiple return values, [similar to `useState`](#reactusestate).
+
+```lua
+local function DisplaysSize(props)
+ local absSize, setAbsSize = React.useBinding(Vector2.new(0, 0))
+ return React.createElement(React.Fragment, nil,
+ React.createElement("ImageLabel", {
+ Image = props.image,
+ [React.Change.AbsoluteSize] = function(rbx)
+ setAbsSize(rbx.AbsoluteSize)
+ end,
+ }),
+ React.createElement("TextLabel", {
+ Text = absSize:map(function(value)
+ return "X = " .. tostring(value.X) .. "; Y = " .. tostring(value.Y)
+ end)
+ }
+ )
+end
+```
+
+A `Binding` has the following API:
+
+#### getValue
+
+!!! warning
+ Using `getValue` inside a component's `render` method is likely to result in using stale values! Using the unwrapped value directly won't allow Roact to subscribe to a binding's updates. To guarantee that a bound value will update, use the binding itself for your prop value or use the `map` method to map the value to a new one.
+
+Returns the internal value of the binding. This is helpful when updating a binding relative to its current value.
+
+```lua
+local function UpdateOnClick()
+ local count, setCount = React.useBinding(0)
+
+ return React.createElement("TextButton", {
+ Text = count,
+ [React.Event.Activated] = function()
+ setCount(count:getValue() + 1)
+ end,
+ })
+end
+```
+
+#### map
+
+Returns a new binding that maps the existing binding's value to something else. For example, `map` can be used to transform an animation progress value like `0.4` into a property that can be consumed by a Roblox Instance like `UDim2.new(0.4, 0, 1, 0)`.
+
+In function components, the `useBinding` hook provides equivalent functionality, and guarantees that it will return the same binding and updater objects on subsequent calls (just like `useRef` does).
+
+```lua
+local function UpdateOnClick()
+ local count, setCount = React.useBinding(0)
+
+ return React.createElement("TextButton", {
+ Text = count:map(function(value)
+ return string.format("Clicked %d times", value)
+ end),
+ [React.Event.Activated] = function()
+ setCount(count:getValue() + 1)
+ end,
+ })
+end
+```
+
+## React.Profiler
+Refer to [React Profiler API documentation](https://beta.reactjs.org/reference/react/Profiler).
+
+## React.StrictMode
+Refer to [React StrictMode API documentation](https://beta.reactjs.org/reference/react/StrictMode).
+
+## React.createMutableSource
+Refer to [relevant React RFC](https://github.com/reactjs/rfcs/pull/147).
+
+## React.useMutableSource
+Refer to [relevant React RFC](https://github.com/reactjs/rfcs/pull/147).
+
+## Roblox and Luau
+
+The React package shipped from Roact includes certain APIs designed specifically to account for nuances of the Luau language and to more easily leverage Roblox engine features.
+
+These APIs are unique to Roact and do not have equivalents in React JS.
+
+### React.None
+
+
+A placeholder value that can be used to remove fields from a table (by changing the value to nil) when merging tables. This allows state fields to be nil-able despite lua treating table fields with `nil` values as semantically equivalent to absent fields.
+
+`React.None` can be used to remove values from React class component state via these uses:
+
+* When returning a table from the updater function passed to a class component's [`setState`]() method
+ ```lua
+ self:setState(function(_prevState)
+ return { myStateValue = React.None }
+ end)
+ ```
+* When passing a table directly to a class component's `setState` method
+ ```lua
+ self:setState({ myStateValue = React.None })
+ ```
+* When returning a table from a component's `getDerivedStateFromProps` implementation
+ ```lua
+ function MyComponent.getDerivedStateFromProps(props, state)
+ return {
+ value = if props.someCondition
+ then state.value
+ else React.None
+ }
+ end
+ ```
+
+!!! caution
+ `React.None` should be used sparingly; component state fields can generally be expressed more clearly with enumerated values or reasonable defaults than with nil-able values.
+
+ Additionally, `React.None` is not intended to be used as a prop value, and may be reverted to nil by internal React logic in some cases if it's provided as one.
+
+### React.Event
+
+
+A special key that can be used to interact with [events on Roblox Instance objects](https://developer.roblox.com/en-us/api-reference/function/Instance/GetPropertyChangedSignal). Index into `Roact.Event` with the name of an Event that's defined for the given host Instance class in order to get a special prop key that connects to that event:
+
+```lua
+Roact.createElement("ImageButton", {
+ [React.Event.MouseButton1Click] = function(rbx, x, y)
+ print(rbx, "clicked at position", x, y)
+ end,
+})
+```
+
+The event connection will be automatically created when the host element is mounted and automatically disconnected when the element is unmounted.
+
+!!! info
+ Event callbacks receive the Roblox Instance as the first parameter, followed by any parameters yielded by the event.
+
+### React.Change
+
+
+A special key that can be used to interact with the [`GetPropertyChangedSignal` method on Roblox Instance objects](https://developer.roblox.com/en-us/api-reference/function/Instance/GetPropertyChangedSignal). Index into `Roact.Change` with the name of the property to get a special prop key that connects to the change signal of an Instance with that property:
+
+```lua
+Roact.createElement("ScrollingFrame", {
+ [React.Change.CanvasPosition] = function(rbx)
+ print("ScrollingFrame scrolled to", rbx.CanvasPosition)
+ end,
+})
+```
+
+The change signal connection will be automatically created when the host element is mounted and automatically disconnected when the element is unmounted.
+
+### React.Tag
+
+
+```lua
+local button = Roact.createElement("TextButton", {
+ [React.Tag] = "confirm-button"
+ Text = "Confirm",
+ -- ...
+})
+```
+
+A special key that can be used to apply [`CollectionService`](https://developer.roblox.com/en-us/api-reference/class/CollectionService) tags to a host component.
+
+Multiple tags can be provided as a single space-delimited string. For example:
+```lua
+[React.Tag] = "some-tag some-other-tag"
+```
+will apply "some-tag" and "some-other-tag" as `CollectionService` tags to the underlying Roblox Instance when the component is mounted.
+
+### React.createBinding
+
+
+Creates a binding object. This function can be used outside of React function components and works similarly to [`useBinding`](#reactusebinding).
+
+Use `createBinding` when you'd like to use a binding within a class component:
+
+```lua
+local DisplaysSize = React.Component:extend("DisplaysSize")
+
+function DisplaysSize:init()
+ self.absSize, self.setAbsSize = React.createBinding(Vector2.new(0, 0))
+end
+
+function DisplaysSize:render()
+ return React.createElement(React.Fragment, nil,
+ React.createElement("ImageLabel", {
+ Image = self.props.image,
+ [React.Change.AbsoluteSize] = function(rbx)
+ self.setAbsSize(rbx.AbsoluteSize)
+ end,
+ }),
+ React.createElement("TextLabel", {
+ Text = self.absSize:map(function(value)
+ return "X = " .. tostring(value.X) .. "; Y = " .. tostring(value.Y)
+ end)
+ }
+ )
+end
+```
+
+### React.joinBindings
+
+
+Combines a map or array of bindings into a single binding. The new binding's value will have the same keys as the input table of bindings. Each time any of the input bindings update, the resulting binding will update as well with a new value.
+
+This capability can be used to create a single binding value that responds to changes from several input bindings:
+```lua
+local function Flex()
+ local aSize, setASize = React.useBinding(Vector2.new())
+ local bSize, setBSize = React.useBinding(Vector2.new())
+
+ return React.createElement(
+ "Frame",
+ {
+ Size = React.joinBindings({aSize, bSize}):map(function(sizes)
+ local sum = Vector2.new()
+
+ for _, size in sizes do
+ sum += size
+ end
+
+ return UDim2.new(0, sum.X, 0, sum.Y)
+ end),
+ },
+ React.createElement("Frame", {
+ Size = UDim2.new(1, 0, 0, 30),
+ [React.Change.AbsoluteSize] = function(instance)
+ setASize(instance.Size)
+ end,
+ }),
+ React.createElement("Frame", {
+ Size = UDim2.new(1, 0, 0, 30),
+ Position = aSize:map(function(size)
+ return UDim2.new(0, 0, 0, size.Y)
+ end),
+ [React.Change.AbsoluteSize] = function(instance)
+ setBSize(instance.Size)
+ end,
+ })
+ )
+end
+```
+
diff --git a/docs/api-reference/roact-compat.md b/docs/api-reference/roact-compat.md
new file mode 100644
index 00000000..b23b3b1b
--- /dev/null
+++ b/docs/api-reference/roact-compat.md
@@ -0,0 +1,151 @@
+# RoactCompat
+
+The `RoactCompat` package is designed to have the same interface as [legacy Roact](https://roblox.github.io/roact/api-reference/). This should allow easier adoption of Roact 17 in existing Roact code.
+
+`RoactCompat` is fully compatible with all Roact 17 logic. In particular, you may wish to use `RoactCompat` in combination with the [`React`](react.md#react) and [`ReactRoblox`](react-roblox.md#reactroblox) packages that provide the new interface.
+
+!!! caution
+ `RoactCompat` is **not** compatible with legacy Roact in any way. It should be used only as a drop-in replacement for legacy Roact, for the purposes of upgrading existing projects written for legacy Roact.
+
+## RoactCompat.Component
+Re-exports [React.Component](react.md#ReactComponent).
+
+## RoactCompat.PureComponent
+Re-exports [React.PureComponent](react.md#ReactPureComponent).
+
+## RoactCompat.createElement
+Re-exports [React.createElement](react.md#ReactcreateElement).
+
+## RoactCompat.createContext
+Re-exports [React.createContext](react.md#ReactcreateContext).
+
+## RoactCompat.createRef
+Re-exports [React.createRef](react.md#ReactcreateRef).
+
+## RoactCompat.forwardRef
+Re-exports [React.forwardRef](react.md#ReactforwardRef).
+
+## RoactCompat.mount
+
+```
+RoactCompat.mount(
+ element: ReactElement,
+ container: Instance?,
+ name: string?
+): RoactTree
+```
+Compatibility method mimicking [legacy `Roact.mount`](https://roblox.github.io/roact/api-reference/#roactmount).
+
+For all intents and purposes, this should function equivalently to legacy Roact's `mount` function. Under the hood, RoactCompat takes the following steps:
+
+1. Creates a root using [`React.createRoot`](react.md#reactcreateroot)
+ * When `_G.__ROACT_17_COMPAT_LEGACY_ROOT__` is enabled, this will use [`React.createLegacyRoot`](react.md#reactcreatelegacyroot) instead
+2. Calls `root:render` with the provided element
+ * React's roots take complete control of the provided container, deleting all existing children. Legacy Roact does not tamper with existing children of the provided container. To mimic the legacy behavior, we use a [`Portal`](react.md#reactcreateportal) to mount into the container instead of providing it directly to the root.
+ * When `_G.__ROACT_17_INLINE_ACT__` is enabled, the `render` call is automatically wrapped in [`ReactRoblox.act`](react-roblox.md#reactrobloxact) to ensure that mounting behavior resolves synchronously in tests.
+3. Returns an opaque handle to the root that can be used with [`RoactCompat.update`](#roactcompatupdate) and [`RoactCompat.unmount`](#roactcompatunmount)
+
+## RoactCompat.update
+
+```
+RoactCompat.update(tree: RoactTree, element: ReactElement): RoactTree
+```
+Compatibility method mimicking [legacy `Roact.update`](https://roblox.github.io/roact/api-reference/#roactupdate).
+
+The first argument should be the value returned from a prior call to [`RoactCompat.mount`](#roactcompatmount) or `RoactCompat.update`. This function will not work if the argument passed in was created with legacy Roact.
+
+## RoactCompat.unmount
+
+```
+RoactCompat.unmount(tree: RoactTreeHandle)
+```
+Compatibility method mimicking [legacy `Roact.unmount`](https://roblox.github.io/roact/api-reference/#roactunmount).
+
+-- API compatibility layers to accommodate old interfaces
+## RoactCompat.createFragment
+
+```
+RoactCompat.createFragment(elements: { [string | number]: ReactElement }): ReactElement
+```
+Compatibility method mimicking [`Roact.createFragment`](https://roblox.github.io/roact/api-reference/#roactcreatefragment). Uses the special component [`React.Fragment`](react.md#reactfragment) under the hood.
+
+## RoactCompat.oneChild
+
+```
+RoactCompat.oneChild(
+ children: { [string | number]: ReactElement } | ReactElement | nil
+): ReactElement
+```
+Compatibility method mimicking [`Roact.oneChild`](https://roblox.github.io/roact/api-reference/#roactonechild). This function is similar to [`React.Children.only`](react.md#reactchildrenonly), but provides additional functionality to unwrap a table that may contain a single element.
+
+## RoactCompat.setGlobalConfig
+
+```
+RoactCompat.setGlobalConfig(configValues: { [string]: boolean })
+```
+Compatibility method mimicking [`Roact.setGlobalConfig`](https://roblox.github.io/roact/api-reference/#roactsetglobalconfig). **This does not apply to Roact 17, so calling this function is a no-op.**
+
+!!! info
+ If you need to apply global configuration to Roact 17, you can do so by setting global values [FIXME LINK TO CONFIGURATION DOC](../configuration.md)
+
+## RoactCompat.Portal
+
+Compatibility component mimicking [`Roact.Portal`](https://roblox.github.io/roact/api-reference/#roactportal). Uses the [React.createPortal](react.md#reactcreateportal) function under the hood.
+
+## RoactCompat.Ref
+
+Compatibility field that mimics the special symbol key [`Roact.Ref`](https://roblox.github.io/roact/api-reference/#roactref). In RoactCompat, the `Ref` field is simply equal to the string "ref", which is a reserved prop key in Roact 17.
+
+This allows prop tables that are written for legacy Roact:
+```lua
+Roact.createElement("TextLabel", {
+ Text = "Hello",
+ [Roact.Ref] = textLabelRef,
+})
+```
+...to be equivalent to prop tables written for Roact 17, which uses "ref" as a reserved prop name:
+```lua
+Roact.createElement("TextLabel", {
+ Text = "Hello",
+ ref = textLabelRef,
+})
+```
+
+## RoactCompat.Children
+
+Compatibility field that mimics the special symbol key [`Roact.Children`](https://roblox.github.io/roact/api-reference/#roactchildren). In RoactCompat, the `Children` field is simply equal to the string "children", which is a reserved prop key in Roact 17.
+
+This allows prop tables that are written for legacy Roact:
+```lua
+-- forwards the children provided to this component
+Roact.createElement("Frame", nil, self.props[Roact.Children])
+```
+...to be equivalent to prop tables written for Roact 17, which uses "children" as a reserved prop name:
+```lua
+-- forwards the children provided to this component
+Roact.createElement("Frame", nil, self.props.children)
+```
+
+!!! caution
+ This is not to be confused with [`React.Children`](react.md#reactchildren), which is a set of utilities for transforming or interacting with sets of children passed to `createElement`.
+
+## RoactCompat.None
+
+Re-exports [React.None](react.md#reactnone).
+
+## RoactCompat.Event
+
+Re-exports [React.Event](react.md#reactevent).
+
+## RoactCompat.Change
+
+Re-exports [React.Change](react.md#reactchange).
+
+## RoactCompat.createBinding
+Re-exports [ReactRoblox.createBinding](react.md#reactcreatebinding).
+
+## RoactCompat.joinBindings
+Re-exports [ReactRoblox.joinBindings](react.md#reactjoinbindings).
+
+## RoactCompat.act
+Re-exports [ReactRoblox.act](react-roblox.md#reactrobloxact).
diff --git a/docs/bench.md b/docs/bench.md
new file mode 100644
index 00000000..77425da0
--- /dev/null
+++ b/docs/bench.md
@@ -0,0 +1,21 @@
+# Benchmarks
+
+
+
+
+ Download data as JSON
+
+
+
+
+
+
diff --git a/docs/bench/data.json b/docs/bench/data.json
new file mode 100644
index 00000000..331dd92d
--- /dev/null
+++ b/docs/bench/data.json
@@ -0,0 +1,19857 @@
+{
+ "lastUpdate": 1645033057748,
+ "repoUrl": "https://github.com/Roblox/roact-alignment",
+ "entries": {
+ "Concurrent Benchmark": [
+ {
+ "commit": {
+ "author": {
+ "email": "37384169+ZoteTheMighty@users.noreply.github.com",
+ "name": "Paul Doyle",
+ "username": "ZoteTheMighty"
+ },
+ "committer": {
+ "email": "noreply@github.com",
+ "name": "GitHub",
+ "username": "web-flow"
+ },
+ "distinct": true,
+ "id": "d46e91e5737cb82041f6086f13eb27fa2766a2a1",
+ "message": "LUAFDN-461 - Fix forward ref in dev (#177)\n\nLUAFDN-461\r\n\r\nFixes an issue where forwardRef objects did not allow fields to be assigned to them, which will break certain use cases in the wild.",
+ "timestamp": "2021-08-16T13:26:12-07:00",
+ "tree_id": "993e932ceffdeee799c21dc5224dab05cd1a6087",
+ "url": "https://github.com/Roblox/roact-alignment/commit/d46e91e5737cb82041f6086f13eb27fa2766a2a1"
+ },
+ "date": 1629145682083,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 61.5777,
+ "range": "±7.80%",
+ "unit": "ops/sec",
+ "extra": "617 samples\nroblox-cli version: ?"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.7619,
+ "range": "±31.19%",
+ "unit": "ms/op",
+ "extra": "617 samples\nroblox-cli version: ?"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 115.5126,
+ "range": "±4.92%",
+ "unit": "sec/op",
+ "extra": "200 samples\nroblox-cli version: ?"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "f854288e5e7aa7f6f5bbfbbb962e8d28d672363f",
+ "message": "Calc bench on pr",
+ "timestamp": "2021-08-19T15:13:11Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/183/commits/f854288e5e7aa7f6f5bbfbbb962e8d28d672363f"
+ },
+ "date": 1629723305117,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 61.9574,
+ "range": "±7.81%",
+ "unit": "ops/sec",
+ "extra": "613 samples\nroblox-cli version: 0.492.0.428906"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.869,
+ "range": "±31.12%",
+ "unit": "ms/op",
+ "extra": "613 samples\nroblox-cli version: 0.492.0.428906"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 114.1414,
+ "range": "±4.97%",
+ "unit": "sec/op",
+ "extra": "200 samples\nroblox-cli version: 0.492.0.428906"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "b05ed983ec8a2bfb8183ac394532cdf26fd396c4",
+ "message": "LUAFDN-496 Wrong Instance Names in Array of Children",
+ "timestamp": "2021-09-01T22:45:36Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/187/commits/b05ed983ec8a2bfb8183ac394532cdf26fd396c4"
+ },
+ "date": 1630616941031,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 61.7646,
+ "range": "±34.74%",
+ "unit": "ops/sec",
+ "extra": "600 samples\nroblox-cli version: 0.493.1.4930375"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.6675,
+ "range": "±226.93%",
+ "unit": "ms/op",
+ "extra": "600 samples\nroblox-cli version: 0.493.1.4930375"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 120.3817,
+ "range": "±24.80%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.493.1.4930375"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "0960cb8107c7599e71e7411d967065bc9fd8d9ad",
+ "message": "LUAFDN-540 Limit pcalls in recursivelyCommitLayoutEffects",
+ "timestamp": "2021-09-03T16:54:44Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/190/commits/0960cb8107c7599e71e7411d967065bc9fd8d9ad"
+ },
+ "date": 1630696317433,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 62.784,
+ "range": "±40.74%",
+ "unit": "ops/sec",
+ "extra": "600 samples\nroblox-cli version: 0.494.0.4940337"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.776,
+ "range": "±224.20%",
+ "unit": "ms/op",
+ "extra": "600 samples\nroblox-cli version: 0.494.0.4940337"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 106.46,
+ "range": "±21.91%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.494.0.4940337"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "89a9413bf48d6f22096a6be5458202c7d6eafb24",
+ "message": "LUAFDN-317 React devtools",
+ "timestamp": "2021-09-03T19:53:44Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/115/commits/89a9413bf48d6f22096a6be5458202c7d6eafb24"
+ },
+ "date": 1630723698313,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 63.1118,
+ "range": "±37.70%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.494.0.4940339"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 23.1141,
+ "range": "±228.12%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.494.0.4940339"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 129.7819,
+ "range": "±27.30%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.494.0.4940339"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "d316007283b3ec76aabda8cc9fc6e6f847afc937",
+ "message": "LUAFDN-537 and LUAFDN-499: fix string formatting in several error messages",
+ "timestamp": "2021-09-04T02:58:11Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/192/commits/d316007283b3ec76aabda8cc9fc6e6f847afc937"
+ },
+ "date": 1631297857604,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 64.2736,
+ "range": "±43.25%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.495.0.4950265"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.5439,
+ "range": "±227.05%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.495.0.4950265"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 108.4224,
+ "range": "±20.38%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.495.0.4950265"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "cdad9e14ed240e308df8ed82ea6913545d5c14da",
+ "message": "LUAFDN-56: Debug tracing API and tests",
+ "timestamp": "2021-09-10T18:47:41Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/143/commits/cdad9e14ed240e308df8ed82ea6913545d5c14da"
+ },
+ "date": 1631306410178,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 63.1338,
+ "range": "±39.58%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.495.0.4950265"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 23.0241,
+ "range": "±228.75%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.495.0.4950265"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 126.4891,
+ "range": "±25.69%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.495.0.4950265"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "f6a2e9ae2764afcfd9958c8aa4cc9bea68f68c81",
+ "message": "Switch back to setTimeout",
+ "timestamp": "2021-09-10T23:31:56Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/195/commits/f6a2e9ae2764afcfd9958c8aa4cc9bea68f68c81"
+ },
+ "date": 1631648269921,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 63.3279,
+ "range": "±39.66%",
+ "unit": "ops/sec",
+ "extra": "603 samples\nroblox-cli version: 0.495.0.4950267"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.7398,
+ "range": "±225.77%",
+ "unit": "ms/op",
+ "extra": "603 samples\nroblox-cli version: 0.495.0.4950267"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 113.3065,
+ "range": "±30.29%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.495.0.4950267"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "47dcf2451a484650001d667ff4ee068f2254ccb9",
+ "message": "LUAFDN-58 Fix additional bugs/quality gaps in DevTools",
+ "timestamp": "2021-09-14T21:12:42Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/197/commits/47dcf2451a484650001d667ff4ee068f2254ccb9"
+ },
+ "date": 1631844120515,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 62.555,
+ "range": "±37.55%",
+ "unit": "ops/sec",
+ "extra": "602 samples\nroblox-cli version: 0.496.0.4960343"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 23.2108,
+ "range": "±226.64%",
+ "unit": "ms/op",
+ "extra": "602 samples\nroblox-cli version: 0.496.0.4960343"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 131.4866,
+ "range": "±24.38%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.496.0.4960343"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "59d9aee9aa9fbaa9573632fc4bf68072d81aae7e",
+ "message": "LUAFDN-538: use task library",
+ "timestamp": "2021-09-17T02:14:27Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/198/commits/59d9aee9aa9fbaa9573632fc4bf68072d81aae7e"
+ },
+ "date": 1632256586311,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 57.3076,
+ "range": "±27.46%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.496.0.4960343"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.8944,
+ "range": "±222.92%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.496.0.4960343"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 83.2509,
+ "range": "±20.82%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.496.0.4960343"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "c74e1b9e25531ce64bcfa805cbe5873f629f925a",
+ "message": "LUAFDN-607: Update MAX_PCALL_DEPTH usage",
+ "timestamp": "2021-09-21T22:08:34Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/201/commits/c74e1b9e25531ce64bcfa805cbe5873f629f925a"
+ },
+ "date": 1632346013343,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 57.4422,
+ "range": "±30.02%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.496.0.4960343"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 23.177,
+ "range": "±219.83%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.496.0.4960343"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 92.2023,
+ "range": "±24.31%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.496.0.4960343"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "5fe5615d45e448a94e57006eb8b8ce6822d85c19",
+ "message": "#nojira - Upgrade polyfill to play nice with in lua-apps",
+ "timestamp": "2021-09-23T16:01:31Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/202/commits/5fe5615d45e448a94e57006eb8b8ce6822d85c19"
+ },
+ "date": 1632425161298,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 57.8238,
+ "range": "±31.48%",
+ "unit": "ops/sec",
+ "extra": "602 samples\nroblox-cli version: 0.496.0.4960343"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 23.3239,
+ "range": "±221.02%",
+ "unit": "ms/op",
+ "extra": "602 samples\nroblox-cli version: 0.496.0.4960343"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 95.9572,
+ "range": "±24.69%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.496.0.4960343"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "11f0938c4b71d4b3d1f11bf27333550cf4d105dd",
+ "message": "#nojira - Put warning behind dev",
+ "timestamp": "2021-09-23T19:52:04Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/203/commits/11f0938c4b71d4b3d1f11bf27333550cf4d105dd"
+ },
+ "date": 1632456047799,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 58.0111,
+ "range": "±33.35%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.497.0.4970357"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 23.0055,
+ "range": "±223.38%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.497.0.4970357"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 84.7814,
+ "range": "±20.51%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.497.0.4970357"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "180e78d05c536fec1e9dee503db30a054db12af1",
+ "message": "#nojira - Update to rc.3",
+ "timestamp": "2021-09-24T04:08:39Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/204/commits/180e78d05c536fec1e9dee503db30a054db12af1"
+ },
+ "date": 1632508694854,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 57.5149,
+ "range": "±26.66%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.497.0.4970357"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.6034,
+ "range": "±224.16%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.497.0.4970357"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 79.5571,
+ "range": "±19.77%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.497.0.4970357"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "6b2b75ac88f3405cbe2892693c8284665c074881",
+ "message": "Sometimes instance names are incorrect",
+ "timestamp": "2021-09-24T18:50:10Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/209/commits/6b2b75ac88f3405cbe2892693c8284665c074881"
+ },
+ "date": 1632769985376,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 59.3411,
+ "range": "±23.43%",
+ "unit": "ops/sec",
+ "extra": "600 samples\nroblox-cli version: 0.497.0.4970358"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 21.6079,
+ "range": "±231.93%",
+ "unit": "ms/op",
+ "extra": "600 samples\nroblox-cli version: 0.497.0.4970358"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 72.1709,
+ "range": "±20.29%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.497.0.4970358"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "41a491696aa3933a35daf6c2b05903e3633c7d4c",
+ "message": "Made types compatible with Luau RFC recursive-type-restriction",
+ "timestamp": "2021-09-27T19:51:58Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/210/commits/41a491696aa3933a35daf6c2b05903e3633c7d4c"
+ },
+ "date": 1632775888609,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 58.4029,
+ "range": "±26.20%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.214,
+ "range": "±226.69%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 74.1752,
+ "range": "±18.10%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "8956b5705618418701bfd118411c4ac606e861b5",
+ "message": "LUAFDN-611: Fix hard runtime errors caused by multiple mistranslation bugs",
+ "timestamp": "2021-09-27T21:17:24Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/206/commits/8956b5705618418701bfd118411c4ac606e861b5"
+ },
+ "date": 1632777759503,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 57.9119,
+ "range": "±32.99%",
+ "unit": "ops/sec",
+ "extra": "602 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 23.6102,
+ "range": "±218.48%",
+ "unit": "ms/op",
+ "extra": "602 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 89.5044,
+ "range": "±24.70%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "8d1d0941fffd01703c6ae30208eb3979fad7c870",
+ "message": "LUAFDN-610: Fix a mistranslation bug that results in a hard runtime error",
+ "timestamp": "2021-09-27T21:35:46Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/205/commits/8d1d0941fffd01703c6ae30208eb3979fad7c870"
+ },
+ "date": 1632779124827,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 58.2772,
+ "range": "±23.24%",
+ "unit": "ops/sec",
+ "extra": "600 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 21.9207,
+ "range": "±230.29%",
+ "unit": "ms/op",
+ "extra": "600 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 76.0529,
+ "range": "±23.19%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "a45b813eba458f0757f081ca36c805c950628cd8",
+ "message": "LUAFDN-247: Type public API more thoroughly",
+ "timestamp": "2021-09-27T21:51:34Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/211/commits/a45b813eba458f0757f081ca36c805c950628cd8"
+ },
+ "date": 1632785097165,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 58.2933,
+ "range": "±29.34%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.2876,
+ "range": "±226.82%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 82.2506,
+ "range": "±16.67%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "6a24d389ce9e63933d437e3ee75ce6431db8959b",
+ "message": "Switch promise dependency to internal one",
+ "timestamp": "2021-09-27T23:52:53Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/212/commits/6a24d389ce9e63933d437e3ee75ce6431db8959b"
+ },
+ "date": 1632798146238,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 58.1224,
+ "range": "±22.29%",
+ "unit": "ops/sec",
+ "extra": "600 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.0294,
+ "range": "±228.31%",
+ "unit": "ms/op",
+ "extra": "600 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 77.4657,
+ "range": "±18.58%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "e7a58f4cfcc4a682fe9479c590b1455ca37c509d",
+ "message": "LUAFDN-498 - Better error output for bad prop assignments",
+ "timestamp": "2021-09-28T06:02:25Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/200/commits/e7a58f4cfcc4a682fe9479c590b1455ca37c509d"
+ },
+ "date": 1632872858159,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 57.1674,
+ "range": "±23.13%",
+ "unit": "ops/sec",
+ "extra": "600 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.4917,
+ "range": "±225.61%",
+ "unit": "ms/op",
+ "extra": "600 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 82.029,
+ "range": "±21.99%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "c645f97ebb36682bb7e8d33d1b22a08b536ca18a",
+ "message": "#nojira Cleanup pragmas, add strict mode where reasonable, fix a bug",
+ "timestamp": "2021-09-29T00:15:19Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/213/commits/c645f97ebb36682bb7e8d33d1b22a08b536ca18a"
+ },
+ "date": 1632878703882,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 57.8072,
+ "range": "±28.84%",
+ "unit": "ops/sec",
+ "extra": "600 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.7196,
+ "range": "±222.41%",
+ "unit": "ms/op",
+ "extra": "600 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 78.1392,
+ "range": "±19.59%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "737e2a29452e082b7398f2f4f561675309147df2",
+ "message": "LUAFDN-622 - Export `act` from the top level; enable it behind tests",
+ "timestamp": "2021-09-29T17:33:55Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/214/commits/737e2a29452e082b7398f2f4f561675309147df2"
+ },
+ "date": 1632961626411,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 57.386,
+ "range": "±26.79%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.7327,
+ "range": "±223.32%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 87.9777,
+ "range": "±20.54%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "1fbcbe16f83b5ee7a654c2ea835ceb7674722e32",
+ "message": "LUAFDN-58: Devtools quality improvements",
+ "timestamp": "2021-09-30T00:34:28Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/215/commits/1fbcbe16f83b5ee7a654c2ea835ceb7674722e32"
+ },
+ "date": 1632964058263,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 57.6201,
+ "range": "±32.38%",
+ "unit": "ops/sec",
+ "extra": "602 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 23.5182,
+ "range": "±220.83%",
+ "unit": "ms/op",
+ "extra": "602 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 100.5898,
+ "range": "±25.34%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "1a62c42d4dbb37c387e9647475a8f6bec33b2432",
+ "message": "LUAFDN-58: inject internals into dev tools if the global is present",
+ "timestamp": "2021-09-30T02:42:41Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/216/commits/1a62c42d4dbb37c387e9647475a8f6bec33b2432"
+ },
+ "date": 1633034431611,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 58.0225,
+ "range": "±26.16%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.3322,
+ "range": "±224.11%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 65.8723,
+ "range": "±16.96%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "7edeedbcccb24cef1e506538d8df35ca2aedea45",
+ "message": "#nojira - Bump version",
+ "timestamp": "2021-09-30T20:42:34Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/217/commits/7edeedbcccb24cef1e506538d8df35ca2aedea45"
+ },
+ "date": 1633037735861,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 57.6921,
+ "range": "±30.02%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.498.0.4980396"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.8155,
+ "range": "±222.39%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.498.0.4980396"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 76.6676,
+ "range": "±18.56%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.498.0.4980396"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "b1224be94e4b5d679e8c950e597953f813bc3adb",
+ "message": "RIDE-5275 React devtools inspector integration",
+ "timestamp": "2021-09-30T21:49:35Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/185/commits/b1224be94e4b5d679e8c950e597953f813bc3adb"
+ },
+ "date": 1633039266712,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 57.1644,
+ "range": "±29.61%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.498.0.4980396"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 23.466,
+ "range": "±220.69%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.498.0.4980396"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 95.8256,
+ "range": "±27.13%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.498.0.4980396"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "b28927f22559f567348efe7f0c63157baedf6a16",
+ "message": "#nojira - Switch promise to proper semver dep",
+ "timestamp": "2021-09-30T22:07:19Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/218/commits/b28927f22559f567348efe7f0c63157baedf6a16"
+ },
+ "date": 1633042757013,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 57.6507,
+ "range": "±27.84%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.498.0.4980396"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.7051,
+ "range": "±222.00%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.498.0.4980396"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 78.7269,
+ "range": "±20.88%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.498.0.4980396"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "3705df6efca1811e9cd025eaecabd2b49d034605",
+ "message": "#nojira - Strict mode seems to stumble sometimes when using the 'convert' command",
+ "timestamp": "2021-09-30T23:05:29Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/219/commits/3705df6efca1811e9cd025eaecabd2b49d034605"
+ },
+ "date": 1633109547593,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 57.2945,
+ "range": "±24.61%",
+ "unit": "ops/sec",
+ "extra": "600 samples\nroblox-cli version: 0.498.0.4980396"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.64,
+ "range": "±224.22%",
+ "unit": "ms/op",
+ "extra": "600 samples\nroblox-cli version: 0.498.0.4980396"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 80.3916,
+ "range": "±20.17%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.498.0.4980396"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "b7a75292e97de978e822d69371f6f1a0435f6cf1",
+ "message": "Remove troublesome optimization",
+ "timestamp": "2021-10-01T17:39:47Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/221/commits/b7a75292e97de978e822d69371f6f1a0435f6cf1"
+ },
+ "date": 1633134826563,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 58.4136,
+ "range": "±22.65%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 21.7897,
+ "range": "±228.82%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 67.0458,
+ "range": "±17.93%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "b76de6b4be3953f6b4da77b36e6d0d4a030609bc",
+ "message": "Adopt jest roblox with rotriever 0.5",
+ "timestamp": "2021-10-02T00:38:57Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/181/commits/b76de6b4be3953f6b4da77b36e6d0d4a030609bc"
+ },
+ "date": 1633358799030,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 57.1711,
+ "range": "±27.20%",
+ "unit": "ops/sec",
+ "extra": "600 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.9264,
+ "range": "±222.88%",
+ "unit": "ms/op",
+ "extra": "600 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 88.7817,
+ "range": "±23.13%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "aeb440bbb8474f606c94dc50ff4342ee76da2e5f",
+ "message": "LUAFDN-642",
+ "timestamp": "2021-10-04T14:53:49Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/223/commits/aeb440bbb8474f606c94dc50ff4342ee76da2e5f"
+ },
+ "date": 1633636747270,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 58.7906,
+ "range": "±22.30%",
+ "unit": "ops/sec",
+ "extra": "600 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 21.7317,
+ "range": "±231.81%",
+ "unit": "ms/op",
+ "extra": "600 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 77.7831,
+ "range": "±22.76%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "63272527248f7791e9aa6c28f975a563c2743af9",
+ "message": "LUAFDN-637 Split compatibility layer warnings",
+ "timestamp": "2021-10-07T20:50:49Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/224/commits/63272527248f7791e9aa6c28f975a563c2743af9"
+ },
+ "date": 1633715655019,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 57.9943,
+ "range": "±25.20%",
+ "unit": "ops/sec",
+ "extra": "602 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.275,
+ "range": "±228.20%",
+ "unit": "ms/op",
+ "extra": "602 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 78.5566,
+ "range": "±24.52%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "d77d7c6b84a4092ce2124bc2aa2ec77fe3693778",
+ "message": "#nojira Fix use*Effect create function type",
+ "timestamp": "2021-10-08T18:05:52Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/225/commits/d77d7c6b84a4092ce2124bc2aa2ec77fe3693778"
+ },
+ "date": 1633719576259,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 57.3081,
+ "range": "±24.56%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.4806,
+ "range": "±224.80%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 84.0414,
+ "range": "±21.44%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "26008c76a91bb7eaa458b7ff756b20fd2f2a4522",
+ "message": "LUAFDN-580: Simple optimizations",
+ "timestamp": "2021-10-08T19:11:22Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/227/commits/26008c76a91bb7eaa458b7ff756b20fd2f2a4522"
+ },
+ "date": 1633733655668,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 59.0893,
+ "range": "±22.02%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 21.5445,
+ "range": "±231.36%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 69.3066,
+ "range": "±19.82%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "a7f90fc02f826dd5240e57c4b351486b64404eab",
+ "message": "#nojira another fix that Alan's in-development analyze feature turned up",
+ "timestamp": "2021-10-08T23:23:58Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/228/commits/a7f90fc02f826dd5240e57c4b351486b64404eab"
+ },
+ "date": 1633738139886,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 56.9271,
+ "range": "±25.10%",
+ "unit": "ops/sec",
+ "extra": "600 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.7,
+ "range": "±223.11%",
+ "unit": "ms/op",
+ "extra": "600 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 85.4012,
+ "range": "±20.57%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "fa677ba23ad2df4754d2721ae1fa2d2217973a8f",
+ "message": "#nojira - Bump version for rc.5",
+ "timestamp": "2021-10-09T00:15:48Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/226/commits/fa677ba23ad2df4754d2721ae1fa2d2217973a8f"
+ },
+ "date": 1633738824210,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 58.0882,
+ "range": "±28.15%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.459,
+ "range": "±225.15%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 79.9764,
+ "range": "±17.91%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "bbbf7f1d1dcdaa33aebf99092913f23ee5829655",
+ "message": "robloxdev-cli convert can fail in strict mode",
+ "timestamp": "2021-10-09T01:49:01Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/229/commits/bbbf7f1d1dcdaa33aebf99092913f23ee5829655"
+ },
+ "date": 1633747700921,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 57.4832,
+ "range": "±31.49%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 23.0788,
+ "range": "±220.69%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 87.3145,
+ "range": "±21.40%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "837d50d8d7f22fdcb28ec02b6e05919d2c50f057",
+ "message": "LUAFDN-623 - Use `act` in compat layer when the `__ROACT_17_INLINE_ACT__` global is enabled",
+ "timestamp": "2021-10-09T02:54:03Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/232/commits/837d50d8d7f22fdcb28ec02b6e05919d2c50f057"
+ },
+ "date": 1634056127998,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 57.5657,
+ "range": "±28.59%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.8167,
+ "range": "±222.80%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 78.7666,
+ "range": "±16.77%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "733f87608ee9d26fe5d0e678a537b7840128a7a5",
+ "message": "LUAFDN-311: Use recycle pool for queue update objects",
+ "timestamp": "2021-10-12T16:50:10Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/231/commits/733f87608ee9d26fe5d0e678a537b7840128a7a5"
+ },
+ "date": 1634078780090,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 60.7028,
+ "range": "±21.75%",
+ "unit": "ops/sec",
+ "extra": "602 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 21.0232,
+ "range": "±237.38%",
+ "unit": "ms/op",
+ "extra": "602 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 63.6544,
+ "range": "±16.25%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "30a019de74755f03651f9d84ac961df1f7ce1d71",
+ "message": "#nojira - Add global for legacy root",
+ "timestamp": "2021-10-12T22:53:00Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/233/commits/30a019de74755f03651f9d84ac961df1f7ce1d71"
+ },
+ "date": 1634083020761,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 58.6708,
+ "range": "±28.32%",
+ "unit": "ops/sec",
+ "extra": "600 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.0282,
+ "range": "±227.77%",
+ "unit": "ms/op",
+ "extra": "600 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 74.7729,
+ "range": "±15.66%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "149598c92cfe72abc16c1b101903b1f65f95882b",
+ "message": "#nojira Work around new Luau recursive type error",
+ "timestamp": "2021-10-13T00:04:10Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/234/commits/149598c92cfe72abc16c1b101903b1f65f95882b"
+ },
+ "date": 1634092703381,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 57.6676,
+ "range": "±23.49%",
+ "unit": "ops/sec",
+ "extra": "600 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.3496,
+ "range": "±226.78%",
+ "unit": "ms/op",
+ "extra": "600 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 84.2389,
+ "range": "±21.01%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "a3fe1e8dd69f257f26fc29f589ae094fdc660a5d",
+ "message": "#nojira - Comment out object pool warning for now",
+ "timestamp": "2021-10-13T15:42:15Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/237/commits/a3fe1e8dd69f257f26fc29f589ae094fdc660a5d"
+ },
+ "date": 1634152384622,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 57.7006,
+ "range": "±31.22%",
+ "unit": "ops/sec",
+ "extra": "602 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 23.277,
+ "range": "±221.41%",
+ "unit": "ms/op",
+ "extra": "602 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 96.6531,
+ "range": "±27.17%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "9c07840d01844b4ac2e044c0e5905a407bb08286",
+ "message": "LUAFDN-311: Moar simple optimizations",
+ "timestamp": "2021-10-13T19:45:06Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/230/commits/9c07840d01844b4ac2e044c0e5905a407bb08286"
+ },
+ "date": 1634154456645,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 59.2114,
+ "range": "±25.82%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 21.7995,
+ "range": "±230.40%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 72.3507,
+ "range": "±14.96%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "4742d67758a17082104fc2bebc5e864d7f7e5055",
+ "message": "LUAFDN-311: Optimization pass",
+ "timestamp": "2021-10-13T19:56:31Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/220/commits/4742d67758a17082104fc2bebc5e864d7f7e5055"
+ },
+ "date": 1634161545349,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 58.0054,
+ "range": "±26.86%",
+ "unit": "ops/sec",
+ "extra": "600 samples\nroblox-cli version: 0.499.0.4990382"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 22.4348,
+ "range": "±225.70%",
+ "unit": "ms/op",
+ "extra": "600 samples\nroblox-cli version: 0.499.0.4990382"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 75.0041,
+ "range": "±19.17%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.499.0.4990382"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "cc4a52d1082b6b9dba96db3ca6cd8a9d98640536",
+ "message": "#nojira Update rotriever.toml for rc.6",
+ "timestamp": "2021-10-13T21:46:59Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/235/commits/cc4a52d1082b6b9dba96db3ca6cd8a9d98640536"
+ },
+ "date": 1634161824007,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 58.8127,
+ "range": "±22.95%",
+ "unit": "ops/sec",
+ "extra": "600 samples\nroblox-cli version: 0.499.0.4990382"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 21.5951,
+ "range": "±230.03%",
+ "unit": "ms/op",
+ "extra": "600 samples\nroblox-cli version: 0.499.0.4990382"
+ },
+ {
+ "name": "FirstRender#Δt",
+ "value": 63.2549,
+ "range": "±15.35%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.499.0.4990382"
+ }
+ ]
+ }
+ ],
+ "Mount Deep Tree": [
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "f854288e5e7aa7f6f5bbfbbb962e8d28d672363f",
+ "message": "Calc bench on pr",
+ "timestamp": "2021-08-19T15:13:11Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/183/commits/f854288e5e7aa7f6f5bbfbbb962e8d28d672363f"
+ },
+ "date": 1629723305910,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#Δt",
+ "value": 49.4806,
+ "range": "±13.85%",
+ "unit": "ms/op",
+ "extra": "49 samples\nroblox-cli version: 0.492.0.428906"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "b05ed983ec8a2bfb8183ac394532cdf26fd396c4",
+ "message": "LUAFDN-496 Wrong Instance Names in Array of Children",
+ "timestamp": "2021-09-01T22:45:36Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/187/commits/b05ed983ec8a2bfb8183ac394532cdf26fd396c4"
+ },
+ "date": 1630616941723,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 19.1426,
+ "range": "±13.01%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.493.1.4930375"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 53.3119,
+ "range": "±16.90%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.493.1.4930375"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "0960cb8107c7599e71e7411d967065bc9fd8d9ad",
+ "message": "LUAFDN-540 Limit pcalls in recursivelyCommitLayoutEffects",
+ "timestamp": "2021-09-03T16:54:44Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/190/commits/0960cb8107c7599e71e7411d967065bc9fd8d9ad"
+ },
+ "date": 1630696318070,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 25.1691,
+ "range": "±10.22%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.494.0.4940337"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 40.1717,
+ "range": "±10.99%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.494.0.4940337"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "89a9413bf48d6f22096a6be5458202c7d6eafb24",
+ "message": "LUAFDN-317 React devtools",
+ "timestamp": "2021-09-03T19:53:44Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/115/commits/89a9413bf48d6f22096a6be5458202c7d6eafb24"
+ },
+ "date": 1630723698936,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 19.3849,
+ "range": "±9.78%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.494.0.4940339"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 52.1111,
+ "range": "±10.81%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.494.0.4940339"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "d316007283b3ec76aabda8cc9fc6e6f847afc937",
+ "message": "LUAFDN-537 and LUAFDN-499: fix string formatting in several error messages",
+ "timestamp": "2021-09-04T02:58:11Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/192/commits/d316007283b3ec76aabda8cc9fc6e6f847afc937"
+ },
+ "date": 1631297858182,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 23.7843,
+ "range": "±8.75%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.495.0.4950265"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 42.4006,
+ "range": "±9.79%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.495.0.4950265"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "cdad9e14ed240e308df8ed82ea6913545d5c14da",
+ "message": "LUAFDN-56: Debug tracing API and tests",
+ "timestamp": "2021-09-10T18:47:41Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/143/commits/cdad9e14ed240e308df8ed82ea6913545d5c14da"
+ },
+ "date": 1631306410805,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 18.7784,
+ "range": "±8.18%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.495.0.4950265"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 53.6171,
+ "range": "±8.50%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.495.0.4950265"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "f6a2e9ae2764afcfd9958c8aa4cc9bea68f68c81",
+ "message": "Switch back to setTimeout",
+ "timestamp": "2021-09-10T23:31:56Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/195/commits/f6a2e9ae2764afcfd9958c8aa4cc9bea68f68c81"
+ },
+ "date": 1631648270586,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 23.0356,
+ "range": "±10.60%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.495.0.4950267"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 44.0827,
+ "range": "±14.91%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.495.0.4950267"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "47dcf2451a484650001d667ff4ee068f2254ccb9",
+ "message": "LUAFDN-58 Fix additional bugs/quality gaps in DevTools",
+ "timestamp": "2021-09-14T21:12:42Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/197/commits/47dcf2451a484650001d667ff4ee068f2254ccb9"
+ },
+ "date": 1631844121275,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 18.468,
+ "range": "±9.75%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.496.0.4960343"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 54.7131,
+ "range": "±10.79%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.496.0.4960343"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "59d9aee9aa9fbaa9573632fc4bf68072d81aae7e",
+ "message": "LUAFDN-538: use task library",
+ "timestamp": "2021-09-17T02:14:27Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/198/commits/59d9aee9aa9fbaa9573632fc4bf68072d81aae7e"
+ },
+ "date": 1632256586980,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 19.3896,
+ "range": "±12.74%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.496.0.4960343"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 52.6942,
+ "range": "±17.94%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.496.0.4960343"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "c74e1b9e25531ce64bcfa805cbe5873f629f925a",
+ "message": "LUAFDN-607: Update MAX_PCALL_DEPTH usage",
+ "timestamp": "2021-09-21T22:08:34Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/201/commits/c74e1b9e25531ce64bcfa805cbe5873f629f925a"
+ },
+ "date": 1632346014121,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 17.1521,
+ "range": "±12.38%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.496.0.4960343"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 59.6752,
+ "range": "±19.26%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.496.0.4960343"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "5fe5615d45e448a94e57006eb8b8ce6822d85c19",
+ "message": "#nojira - Upgrade polyfill to play nice with in lua-apps",
+ "timestamp": "2021-09-23T16:01:31Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/202/commits/5fe5615d45e448a94e57006eb8b8ce6822d85c19"
+ },
+ "date": 1632425162036,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 17.1504,
+ "range": "±12.25%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.496.0.4960343"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 59.4611,
+ "range": "±16.71%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.496.0.4960343"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "11f0938c4b71d4b3d1f11bf27333550cf4d105dd",
+ "message": "#nojira - Put warning behind dev",
+ "timestamp": "2021-09-23T19:52:04Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/203/commits/11f0938c4b71d4b3d1f11bf27333550cf4d105dd"
+ },
+ "date": 1632456048508,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 18.217,
+ "range": "±13.76%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970357"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 56.4091,
+ "range": "±20.40%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970357"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "180e78d05c536fec1e9dee503db30a054db12af1",
+ "message": "#nojira - Update to rc.3",
+ "timestamp": "2021-09-24T04:08:39Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/204/commits/180e78d05c536fec1e9dee503db30a054db12af1"
+ },
+ "date": 1632508695528,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 19.4541,
+ "range": "±14.33%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970357"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 52.7708,
+ "range": "±19.12%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970357"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "6b2b75ac88f3405cbe2892693c8284665c074881",
+ "message": "Sometimes instance names are incorrect",
+ "timestamp": "2021-09-24T18:50:10Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/209/commits/6b2b75ac88f3405cbe2892693c8284665c074881"
+ },
+ "date": 1632769985971,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 21.3806,
+ "range": "±13.61%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970358"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 47.9499,
+ "range": "±18.91%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970358"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "41a491696aa3933a35daf6c2b05903e3633c7d4c",
+ "message": "Made types compatible with Luau RFC recursive-type-restriction",
+ "timestamp": "2021-09-27T19:51:58Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/210/commits/41a491696aa3933a35daf6c2b05903e3633c7d4c"
+ },
+ "date": 1632775889165,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 20.195,
+ "range": "±11.81%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 50.4792,
+ "range": "±16.90%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "8956b5705618418701bfd118411c4ac606e861b5",
+ "message": "LUAFDN-611: Fix hard runtime errors caused by multiple mistranslation bugs",
+ "timestamp": "2021-09-27T21:17:24Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/206/commits/8956b5705618418701bfd118411c4ac606e861b5"
+ },
+ "date": 1632777760247,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 17.9105,
+ "range": "±14.42%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 57.5168,
+ "range": "±21.67%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "8d1d0941fffd01703c6ae30208eb3979fad7c870",
+ "message": "LUAFDN-610: Fix a mistranslation bug that results in a hard runtime error",
+ "timestamp": "2021-09-27T21:35:46Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/205/commits/8d1d0941fffd01703c6ae30208eb3979fad7c870"
+ },
+ "date": 1632779125481,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 20.2977,
+ "range": "±14.71%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 50.6438,
+ "range": "±19.90%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "a45b813eba458f0757f081ca36c805c950628cd8",
+ "message": "LUAFDN-247: Type public API more thoroughly",
+ "timestamp": "2021-09-27T21:51:34Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/211/commits/a45b813eba458f0757f081ca36c805c950628cd8"
+ },
+ "date": 1632785097712,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 20.1083,
+ "range": "±11.70%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 50.5223,
+ "range": "±13.91%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "6a24d389ce9e63933d437e3ee75ce6431db8959b",
+ "message": "Switch promise dependency to internal one",
+ "timestamp": "2021-09-27T23:52:53Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/212/commits/6a24d389ce9e63933d437e3ee75ce6431db8959b"
+ },
+ "date": 1632798146948,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 19.6813,
+ "range": "±12.21%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 51.789,
+ "range": "±16.08%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "e7a58f4cfcc4a682fe9479c590b1455ca37c509d",
+ "message": "LUAFDN-498 - Better error output for bad prop assignments",
+ "timestamp": "2021-09-28T06:02:25Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/200/commits/e7a58f4cfcc4a682fe9479c590b1455ca37c509d"
+ },
+ "date": 1632872858812,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 19.8622,
+ "range": "±13.74%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 51.7234,
+ "range": "±20.49%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "c645f97ebb36682bb7e8d33d1b22a08b536ca18a",
+ "message": "#nojira Cleanup pragmas, add strict mode where reasonable, fix a bug",
+ "timestamp": "2021-09-29T00:15:19Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/213/commits/c645f97ebb36682bb7e8d33d1b22a08b536ca18a"
+ },
+ "date": 1632878704555,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 19.9107,
+ "range": "±12.77%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 51.3202,
+ "range": "±17.47%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "737e2a29452e082b7398f2f4f561675309147df2",
+ "message": "LUAFDN-622 - Export `act` from the top level; enable it behind tests",
+ "timestamp": "2021-09-29T17:33:55Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/214/commits/737e2a29452e082b7398f2f4f561675309147df2"
+ },
+ "date": 1632961627152,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 17.2068,
+ "range": "±14.33%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 59.7636,
+ "range": "±20.60%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "1fbcbe16f83b5ee7a654c2ea835ceb7674722e32",
+ "message": "LUAFDN-58: Devtools quality improvements",
+ "timestamp": "2021-09-30T00:34:28Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/215/commits/1fbcbe16f83b5ee7a654c2ea835ceb7674722e32"
+ },
+ "date": 1632964058847,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 15.5272,
+ "range": "±15.53%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 66.6555,
+ "range": "±22.94%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "1a62c42d4dbb37c387e9647475a8f6bec33b2432",
+ "message": "LUAFDN-58: inject internals into dev tools if the global is present",
+ "timestamp": "2021-09-30T02:42:41Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/216/commits/1a62c42d4dbb37c387e9647475a8f6bec33b2432"
+ },
+ "date": 1633034432263,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 23.8607,
+ "range": "±14.01%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 43.0321,
+ "range": "±19.89%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "7edeedbcccb24cef1e506538d8df35ca2aedea45",
+ "message": "#nojira - Bump version",
+ "timestamp": "2021-09-30T20:42:34Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/217/commits/7edeedbcccb24cef1e506538d8df35ca2aedea45"
+ },
+ "date": 1633037736411,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 19.793,
+ "range": "±12.31%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980396"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 51.6659,
+ "range": "±19.25%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980396"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "b1224be94e4b5d679e8c950e597953f813bc3adb",
+ "message": "RIDE-5275 React devtools inspector integration",
+ "timestamp": "2021-09-30T21:49:35Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/185/commits/b1224be94e4b5d679e8c950e597953f813bc3adb"
+ },
+ "date": 1633039267496,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 16.5457,
+ "range": "±13.06%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980396"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 62.0302,
+ "range": "±20.47%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980396"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "b28927f22559f567348efe7f0c63157baedf6a16",
+ "message": "#nojira - Switch promise to proper semver dep",
+ "timestamp": "2021-09-30T22:07:19Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/218/commits/b28927f22559f567348efe7f0c63157baedf6a16"
+ },
+ "date": 1633042757632,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 19.3586,
+ "range": "±12.60%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980396"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 52.9222,
+ "range": "±19.54%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980396"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "3705df6efca1811e9cd025eaecabd2b49d034605",
+ "message": "#nojira - Strict mode seems to stumble sometimes when using the 'convert' command",
+ "timestamp": "2021-09-30T23:05:29Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/219/commits/3705df6efca1811e9cd025eaecabd2b49d034605"
+ },
+ "date": 1633109548286,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 18.918,
+ "range": "±15.36%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980396"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 54.5209,
+ "range": "±21.01%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980396"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "b7a75292e97de978e822d69371f6f1a0435f6cf1",
+ "message": "Remove troublesome optimization",
+ "timestamp": "2021-10-01T17:39:47Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/221/commits/b7a75292e97de978e822d69371f6f1a0435f6cf1"
+ },
+ "date": 1633134827136,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 22.9928,
+ "range": "±12.81%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 44.5838,
+ "range": "±19.99%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "b76de6b4be3953f6b4da77b36e6d0d4a030609bc",
+ "message": "Adopt jest roblox with rotriever 0.5",
+ "timestamp": "2021-10-02T00:38:57Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/181/commits/b76de6b4be3953f6b4da77b36e6d0d4a030609bc"
+ },
+ "date": 1633358799929,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 18.0961,
+ "range": "±11.85%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 56.2743,
+ "range": "±16.00%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "aeb440bbb8474f606c94dc50ff4342ee76da2e5f",
+ "message": "LUAFDN-642",
+ "timestamp": "2021-10-04T14:53:49Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/223/commits/aeb440bbb8474f606c94dc50ff4342ee76da2e5f"
+ },
+ "date": 1633636748015,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 20.3527,
+ "range": "±13.56%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 50.2368,
+ "range": "±17.16%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "63272527248f7791e9aa6c28f975a563c2743af9",
+ "message": "LUAFDN-637 Split compatibility layer warnings",
+ "timestamp": "2021-10-07T20:50:49Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/224/commits/63272527248f7791e9aa6c28f975a563c2743af9"
+ },
+ "date": 1633715655737,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 21.6928,
+ "range": "±12.42%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 46.984,
+ "range": "±15.84%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "d77d7c6b84a4092ce2124bc2aa2ec77fe3693778",
+ "message": "#nojira Fix use*Effect create function type",
+ "timestamp": "2021-10-08T18:05:52Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/225/commits/d77d7c6b84a4092ce2124bc2aa2ec77fe3693778"
+ },
+ "date": 1633719576923,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 19.7853,
+ "range": "±12.71%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 51.6291,
+ "range": "±17.59%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "26008c76a91bb7eaa458b7ff756b20fd2f2a4522",
+ "message": "LUAFDN-580: Simple optimizations",
+ "timestamp": "2021-10-08T19:11:22Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/227/commits/26008c76a91bb7eaa458b7ff756b20fd2f2a4522"
+ },
+ "date": 1633733656383,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 22.9915,
+ "range": "±12.44%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 44.4146,
+ "range": "±17.56%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "a7f90fc02f826dd5240e57c4b351486b64404eab",
+ "message": "#nojira another fix that Alan's in-development analyze feature turned up",
+ "timestamp": "2021-10-08T23:23:58Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/228/commits/a7f90fc02f826dd5240e57c4b351486b64404eab"
+ },
+ "date": 1633738140518,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 18.3246,
+ "range": "±12.59%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 55.5537,
+ "range": "±14.60%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "fa677ba23ad2df4754d2721ae1fa2d2217973a8f",
+ "message": "#nojira - Bump version for rc.5",
+ "timestamp": "2021-10-09T00:15:48Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/226/commits/fa677ba23ad2df4754d2721ae1fa2d2217973a8f"
+ },
+ "date": 1633738824824,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 18.4752,
+ "range": "±12.99%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 55.477,
+ "range": "±20.00%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "bbbf7f1d1dcdaa33aebf99092913f23ee5829655",
+ "message": "robloxdev-cli convert can fail in strict mode",
+ "timestamp": "2021-10-09T01:49:01Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/229/commits/bbbf7f1d1dcdaa33aebf99092913f23ee5829655"
+ },
+ "date": 1633747701500,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 17.5626,
+ "range": "±11.59%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 57.8745,
+ "range": "±14.70%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "837d50d8d7f22fdcb28ec02b6e05919d2c50f057",
+ "message": "LUAFDN-623 - Use `act` in compat layer when the `__ROACT_17_INLINE_ACT__` global is enabled",
+ "timestamp": "2021-10-09T02:54:03Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/232/commits/837d50d8d7f22fdcb28ec02b6e05919d2c50f057"
+ },
+ "date": 1634056128797,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 19.8421,
+ "range": "±10.95%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 51.1523,
+ "range": "±13.91%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "733f87608ee9d26fe5d0e678a537b7840128a7a5",
+ "message": "LUAFDN-311: Use recycle pool for queue update objects",
+ "timestamp": "2021-10-12T16:50:10Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/231/commits/733f87608ee9d26fe5d0e678a537b7840128a7a5"
+ },
+ "date": 1634078780701,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 24.3762,
+ "range": "±12.65%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 41.848,
+ "range": "±16.50%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "30a019de74755f03651f9d84ac961df1f7ce1d71",
+ "message": "#nojira - Add global for legacy root",
+ "timestamp": "2021-10-12T22:53:00Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/233/commits/30a019de74755f03651f9d84ac961df1f7ce1d71"
+ },
+ "date": 1634083021404,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 19.3447,
+ "range": "±13.20%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 53.025,
+ "range": "±19.84%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "149598c92cfe72abc16c1b101903b1f65f95882b",
+ "message": "#nojira Work around new Luau recursive type error",
+ "timestamp": "2021-10-13T00:04:10Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/234/commits/149598c92cfe72abc16c1b101903b1f65f95882b"
+ },
+ "date": 1634092704038,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 18.953,
+ "range": "±14.16%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 54.2651,
+ "range": "±21.25%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "a3fe1e8dd69f257f26fc29f589ae094fdc660a5d",
+ "message": "#nojira - Comment out object pool warning for now",
+ "timestamp": "2021-10-13T15:42:15Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/237/commits/a3fe1e8dd69f257f26fc29f589ae094fdc660a5d"
+ },
+ "date": 1634152385433,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 16.6561,
+ "range": "±12.30%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 61.331,
+ "range": "±17.86%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "9c07840d01844b4ac2e044c0e5905a407bb08286",
+ "message": "LUAFDN-311: Moar simple optimizations",
+ "timestamp": "2021-10-13T19:45:06Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/230/commits/9c07840d01844b4ac2e044c0e5905a407bb08286"
+ },
+ "date": 1634154457252,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 20.2453,
+ "range": "±10.59%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 50.1186,
+ "range": "±14.46%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "4742d67758a17082104fc2bebc5e864d7f7e5055",
+ "message": "LUAFDN-311: Optimization pass",
+ "timestamp": "2021-10-13T19:56:31Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/220/commits/4742d67758a17082104fc2bebc5e864d7f7e5055"
+ },
+ "date": 1634161545989,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 20.0616,
+ "range": "±11.71%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.499.0.4990382"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 50.8742,
+ "range": "±18.22%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.499.0.4990382"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "cc4a52d1082b6b9dba96db3ca6cd8a9d98640536",
+ "message": "#nojira Update rotriever.toml for rc.6",
+ "timestamp": "2021-10-13T21:46:59Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/235/commits/cc4a52d1082b6b9dba96db3ca6cd8a9d98640536"
+ },
+ "date": 1634161824605,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 23.6701,
+ "range": "±11.97%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.499.0.4990382"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 43.1308,
+ "range": "±18.23%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.499.0.4990382"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "83a6aba3009e4c710e244395cb5cc65ae19351fa",
+ "message": "ci: update benchmarks workflow",
+ "timestamp": "2021-10-13T21:55:02Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/238/commits/83a6aba3009e4c710e244395cb5cc65ae19351fa"
+ },
+ "date": 1634750347826,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 19.6275,
+ "range": "±12.76%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 52.0989,
+ "range": "±18.34%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalInstructionsExecuted",
+ "value": 1030689534,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0029015915,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000781467,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryReads",
+ "value": 259669968,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0293031037,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0005572651,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryWrites",
+ "value": 134462954,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0131739855,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0074690015,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalInstructionsExecuted",
+ "value": 1202013220,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0032883349,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000657314,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryReads",
+ "value": 312215952,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataReadCacheMisses",
+ "value": 0.032018806,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0004595633,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryWrites",
+ "value": 155762063,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0129533531,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0066312874,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "2b8ebac9d357b22453c27aebc5c446932d49a6c7",
+ "message": "Change thresholds to udpate FPS branch",
+ "timestamp": "2021-10-28T18:43:06Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/242/commits/2b8ebac9d357b22453c27aebc5c446932d49a6c7"
+ },
+ "date": 1635446935540,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 18.9257,
+ "range": "±11.94%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 53.7447,
+ "range": "±14.78%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalInstructionsExecuted",
+ "value": 1021921513,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0028436646,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000736377,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryReads",
+ "value": 256451163,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0296323944,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0005348621,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryWrites",
+ "value": 131897871,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0133997007,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0076664846,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalInstructionsExecuted",
+ "value": 1195187138,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0031777383,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.000063001,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryReads",
+ "value": 309437019,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0325511667,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0004390877,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryWrites",
+ "value": 153368355,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0129412877,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.006719913,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "f5858b79139430febb1f073f2182654db08bd637",
+ "message": "LUAFDN-694: eliminate unnecessary allocations",
+ "timestamp": "2021-10-28T19:24:12Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/241/commits/f5858b79139430febb1f073f2182654db08bd637"
+ },
+ "date": 1635543018749,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 23.9269,
+ "range": "±12.30%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 42.6981,
+ "range": "±18.04%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalInstructionsExecuted",
+ "value": 1031013230,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0030065356,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000731746,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryReads",
+ "value": 258748594,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0291399767,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0005268473,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryWrites",
+ "value": 132113750,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0133910664,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0071667786,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalInstructionsExecuted",
+ "value": 1204371419,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0033737632,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000630287,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryReads",
+ "value": 312038573,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0320213713,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0004415223,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryWrites",
+ "value": 152757551,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0130743586,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0072015294,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "15ddcfb84d3d69fe5f4937931a783ff1ec3df930",
+ "message": "Fix Mysterious top bar disappearing bug",
+ "timestamp": "2021-10-30T01:46:20Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/249/commits/15ddcfb84d3d69fe5f4937931a783ff1ec3df930"
+ },
+ "date": 1635830683784,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 21.4616,
+ "range": "±12.36%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 47.5038,
+ "range": "±16.57%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalInstructionsExecuted",
+ "value": 1022537276,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0030209764,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000712375,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryReads",
+ "value": 256490456,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0293581216,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0004763608,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryWrites",
+ "value": 130598950,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0135188606,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0077143576,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalInstructionsExecuted",
+ "value": 1195948459,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0033777551,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000630612,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryReads",
+ "value": 309617950,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0318998882,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0004075765,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryWrites",
+ "value": 151387804,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0131723359,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0067800706,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "9d261c78bbe3ce612749e4e8823c918d4acf8c31",
+ "message": "LUAFDN-692: Add assertions around the instance map to detect bad objects being inserted",
+ "timestamp": "2021-11-02T16:35:50Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/248/commits/9d261c78bbe3ce612749e4e8823c918d4acf8c31"
+ },
+ "date": 1635971043478,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 18.2311,
+ "range": "±10.54%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 55.5256,
+ "range": "±12.14%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalInstructionsExecuted",
+ "value": 1049578164,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0029319408,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000770967,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryReads",
+ "value": 264889581,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0300123431,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0005511957,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryWrites",
+ "value": 132452437,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0133476895,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0075636207,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalInstructionsExecuted",
+ "value": 1244524163,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0032330638,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000647203,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryReads",
+ "value": 324943625,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0328806328,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0004439139,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryWrites",
+ "value": 154235062,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0129125698,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0066830718,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "f683410321e0b3eb3d4d893d1e3dd3692ef35e85",
+ "message": "LUAFDN-698: Fix possible memory leak in RobloxComponentProps.lua",
+ "timestamp": "2021-11-03T20:31:27Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/244/commits/f683410321e0b3eb3d4d893d1e3dd3692ef35e85"
+ },
+ "date": 1635977835488,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 21.3895,
+ "range": "±11.31%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 47.4743,
+ "range": "±14.51%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalInstructionsExecuted",
+ "value": 1048066632,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0029425333,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000719878,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryReads",
+ "value": 264442653,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0299227372,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0005150153,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryWrites",
+ "value": 132210659,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.013343909,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0076363586,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalInstructionsExecuted",
+ "value": 1248025208,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0032368425,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000638152,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryReads",
+ "value": 325906641,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0335232015,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0004459713,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryWrites",
+ "value": 154852277,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0130218169,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.007089137,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "eb240e688e5745b3493f682736278caf005dd4d0",
+ "message": "#nojira - Cut version rc.7",
+ "timestamp": "2021-11-04T15:57:57Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/251/commits/eb240e688e5745b3493f682736278caf005dd4d0"
+ },
+ "date": 1636041793048,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 21.1493,
+ "range": "±13.81%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 48.557,
+ "range": "±20.62%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalInstructionsExecuted",
+ "value": 1048484967,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0029442005,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000721574,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryReads",
+ "value": 264545873,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0300131577,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0005199363,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryWrites",
+ "value": 132256168,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0135014119,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0076326194,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalInstructionsExecuted",
+ "value": 1247849753,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0032281322,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000649525,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryReads",
+ "value": 325856837,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0330950153,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0004473314,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryWrites",
+ "value": 154680146,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0130421586,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.007087503,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "353d64f2776e697b033af9735db7c32fc196255c",
+ "message": "#nojira Emergency fix to try and unblock the reverse merge",
+ "timestamp": "2021-11-04T16:32:04Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/253/commits/353d64f2776e697b033af9735db7c32fc196255c"
+ },
+ "date": 1636057640092,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 18.3907,
+ "range": "±10.86%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 55.1392,
+ "range": "±13.43%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalInstructionsExecuted",
+ "value": 1052700910,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0029247006,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000766761,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryReads",
+ "value": 265711984,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0296561332,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0005501634,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryWrites",
+ "value": 132795775,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0133199644,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0075462717,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalInstructionsExecuted",
+ "value": 1250695870,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0032288505,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000601865,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryReads",
+ "value": 326671736,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0324903652,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0004205445,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryWrites",
+ "value": 154966744,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.012809729,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0066273574,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "d4ea3c7eb4e9d35d3428b6c0e88ef2ac2acdf2af",
+ "message": "Fix __DISABLE_ALL_WARNINGS_EXCEPT_PROP_VALIDATION__",
+ "timestamp": "2021-11-04T20:38:54Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/255/commits/d4ea3c7eb4e9d35d3428b6c0e88ef2ac2acdf2af"
+ },
+ "date": 1636580041539,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 16.0205,
+ "range": "±11.77%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 63.3069,
+ "range": "±12.44%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalInstructionsExecuted",
+ "value": 1043216649,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0028875584,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000743824,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryReads",
+ "value": 263166071,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0296488144,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.000534024,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryWrites",
+ "value": 132048896,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0133663064,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0076274398,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalInstructionsExecuted",
+ "value": 1238063569,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0031937698,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000629572,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryReads",
+ "value": 323142231,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0326075176,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0004419447,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryWrites",
+ "value": 153853735,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0129322892,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0071504536,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "c0df797acc7f79c1e0928d1ac7ab884b156b34e5",
+ "message": "LUAFDN-723: Manually massage non-string errors into strings before re-throwing",
+ "timestamp": "2021-11-17T19:13:06Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/254/commits/c0df797acc7f79c1e0928d1ac7ab884b156b34e5"
+ },
+ "date": 1637179366836,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 16.1194,
+ "range": "±10.03%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 62.666,
+ "range": "±10.48%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalInstructionsExecuted",
+ "value": 1040646121,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0029302958,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000780246,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryReads",
+ "value": 262120281,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0294453827,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0005713064,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryWrites",
+ "value": 131418268,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0133451158,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0076431155,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalInstructionsExecuted",
+ "value": 1234301450,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0032582413,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000662812,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryReads",
+ "value": 321790681,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0325311316,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0004658525,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryWrites",
+ "value": 153311458,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.012982787,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0071883668,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "0408a7e730e0d979915f8e2cc362cdd6a3e2f65d",
+ "message": "Further optimizations",
+ "timestamp": "2021-11-17T20:11:34Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/240/commits/0408a7e730e0d979915f8e2cc362cdd6a3e2f65d"
+ },
+ "date": 1637191572613,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 18.5233,
+ "range": "±13.95%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 54.9535,
+ "range": "±14.02%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalInstructionsExecuted",
+ "value": 1025180433,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0029329393,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000706715,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryReads",
+ "value": 258134901,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0293735832,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0004648074,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryWrites",
+ "value": 129934563,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0134082954,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0072892153,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalInstructionsExecuted",
+ "value": 1216564054,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0032216972,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000630431,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryReads",
+ "value": 317378571,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0322329134,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0004148925,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryWrites",
+ "value": 151780624,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0129877777,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0072466628,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "25e7dcd87c9987a2053ec0a52efc99460a024688",
+ "message": "Separate scheduler mocking from act inlining",
+ "timestamp": "2021-11-23T18:15:18Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/259/commits/25e7dcd87c9987a2053ec0a52efc99460a024688"
+ },
+ "date": 1638223875987,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 16.3643,
+ "range": "±10.55%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 61.7729,
+ "range": "±10.82%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalInstructionsExecuted",
+ "value": 1028934007,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0028816902,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000708199,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryReads",
+ "value": 259360110,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0295690266,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0001618213,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryWrites",
+ "value": 130607301,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.01326213,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0075014413,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalInstructionsExecuted",
+ "value": 1218197861,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0031640213,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000625194,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryReads",
+ "value": 317998590,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0323812819,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataReadCacheMisses",
+ "value": 0.000156573,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryWrites",
+ "value": 152035621,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0127775122,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0070743948,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "a8f8c4933fcae79ebfbd498723b680fb195be5c2",
+ "message": "LUAFDN-257: Backfilling tests",
+ "timestamp": "2021-11-29T23:50:37Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/258/commits/a8f8c4933fcae79ebfbd498723b680fb195be5c2"
+ },
+ "date": 1638303803608,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 16.7532,
+ "range": "±12.05%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 60.4718,
+ "range": "±11.78%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalInstructionsExecuted",
+ "value": 1030176193,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0028746879,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000705336,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryReads",
+ "value": 259725749,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0296853009,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0001488532,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryWrites",
+ "value": 130731318,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0132517137,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0069951716,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalInstructionsExecuted",
+ "value": 1221084557,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0031679788,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000625182,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryReads",
+ "value": 318751424,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataReadCacheMisses",
+ "value": 0.032328643,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0001567334,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryWrites",
+ "value": 152462482,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0127472475,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0070655087,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "ee62df85c6072dd919a664ffcde25cfac4367380",
+ "message": "#nojira - Provide properly-formatted args to cachegrind",
+ "timestamp": "2021-12-13T22:01:30Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/266/commits/ee62df85c6072dd919a664ffcde25cfac4367380"
+ },
+ "date": 1642635785363,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 18.5201,
+ "range": "±10.18%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 54.5494,
+ "range": "±10.57%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalInstructionsExecuted",
+ "value": 988167725,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0029272672,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000733023,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryReads",
+ "value": 249761921,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.029796836,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0001588152,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryWrites",
+ "value": 126068573,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0132041552,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0073139878,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalInstructionsExecuted",
+ "value": 1178113825,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0032149364,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000619524,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryReads",
+ "value": 307901443,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataReadCacheMisses",
+ "value": 0.032896192,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0001539649,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryWrites",
+ "value": 147985249,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0128441991,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0073420629,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "ae436d269232d09c76caa2e70a48247d7f788d00",
+ "message": "LUAFDN-707 Fix useCallback hook in DEV mode",
+ "timestamp": "2021-12-13T22:01:30Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/269/commits/ae436d269232d09c76caa2e70a48247d7f788d00"
+ },
+ "date": 1645033057362,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 20.052,
+ "range": "±10.93%",
+ "unit": "ops/sec",
+ "extra": "100 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 50.5125,
+ "range": "±12.37%",
+ "unit": "ms/op",
+ "extra": "100 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalInstructionsExecuted",
+ "value": 990936414,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0028366563,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000738514,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryReads",
+ "value": 250831131,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0302572969,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0001846103,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#TotalMemoryWrites",
+ "value": 126689758,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0132939476,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0077697678,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalInstructionsExecuted",
+ "value": 1177679974,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0031189832,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000624041,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryReads",
+ "value": 308161517,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0328328829,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0001598123,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#TotalMemoryWrites",
+ "value": 148530404,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0127797269,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountDeepTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.007296082,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ }
+ ]
+ }
+ ],
+ "Mount Wide Tree": [
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "f854288e5e7aa7f6f5bbfbbb962e8d28d672363f",
+ "message": "Calc bench on pr",
+ "timestamp": "2021-08-19T15:13:11Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/183/commits/f854288e5e7aa7f6f5bbfbbb962e8d28d672363f"
+ },
+ "date": 1629723306055,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#Δt",
+ "value": 65.079,
+ "range": "±13.50%",
+ "unit": "ms/op",
+ "extra": "49 samples\nroblox-cli version: 0.492.0.428906"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "b05ed983ec8a2bfb8183ac394532cdf26fd396c4",
+ "message": "LUAFDN-496 Wrong Instance Names in Array of Children",
+ "timestamp": "2021-09-01T22:45:36Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/187/commits/b05ed983ec8a2bfb8183ac394532cdf26fd396c4"
+ },
+ "date": 1630616942363,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 14.8802,
+ "range": "±16.05%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.493.1.4930375"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 68.8792,
+ "range": "±16.53%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.493.1.4930375"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "0960cb8107c7599e71e7411d967065bc9fd8d9ad",
+ "message": "LUAFDN-540 Limit pcalls in recursivelyCommitLayoutEffects",
+ "timestamp": "2021-09-03T16:54:44Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/190/commits/0960cb8107c7599e71e7411d967065bc9fd8d9ad"
+ },
+ "date": 1630696318737,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 17.8774,
+ "range": "±12.05%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.494.0.4940337"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 57.0225,
+ "range": "±16.26%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.494.0.4940337"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "89a9413bf48d6f22096a6be5458202c7d6eafb24",
+ "message": "LUAFDN-317 React devtools",
+ "timestamp": "2021-09-03T19:53:44Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/115/commits/89a9413bf48d6f22096a6be5458202c7d6eafb24"
+ },
+ "date": 1630723699580,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 14.371,
+ "range": "±11.40%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.494.0.4940339"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 70.5449,
+ "range": "±12.43%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.494.0.4940339"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "d316007283b3ec76aabda8cc9fc6e6f847afc937",
+ "message": "LUAFDN-537 and LUAFDN-499: fix string formatting in several error messages",
+ "timestamp": "2021-09-04T02:58:11Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/192/commits/d316007283b3ec76aabda8cc9fc6e6f847afc937"
+ },
+ "date": 1631297858733,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 17.3949,
+ "range": "±10.87%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.495.0.4950265"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 58.3581,
+ "range": "±14.00%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.495.0.4950265"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "cdad9e14ed240e308df8ed82ea6913545d5c14da",
+ "message": "LUAFDN-56: Debug tracing API and tests",
+ "timestamp": "2021-09-10T18:47:41Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/143/commits/cdad9e14ed240e308df8ed82ea6913545d5c14da"
+ },
+ "date": 1631306411439,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 13.9546,
+ "range": "±10.85%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.495.0.4950265"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 72.6083,
+ "range": "±12.35%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.495.0.4950265"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "f6a2e9ae2764afcfd9958c8aa4cc9bea68f68c81",
+ "message": "Switch back to setTimeout",
+ "timestamp": "2021-09-10T23:31:56Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/195/commits/f6a2e9ae2764afcfd9958c8aa4cc9bea68f68c81"
+ },
+ "date": 1631648271245,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 16.9239,
+ "range": "±12.47%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.495.0.4950267"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 60.3474,
+ "range": "±17.04%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.495.0.4950267"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "47dcf2451a484650001d667ff4ee068f2254ccb9",
+ "message": "LUAFDN-58 Fix additional bugs/quality gaps in DevTools",
+ "timestamp": "2021-09-14T21:12:42Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/197/commits/47dcf2451a484650001d667ff4ee068f2254ccb9"
+ },
+ "date": 1631844122035,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 13.5503,
+ "range": "±11.15%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.496.0.4960343"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 74.718,
+ "range": "±11.48%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.496.0.4960343"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "59d9aee9aa9fbaa9573632fc4bf68072d81aae7e",
+ "message": "LUAFDN-538: use task library",
+ "timestamp": "2021-09-17T02:14:27Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/198/commits/59d9aee9aa9fbaa9573632fc4bf68072d81aae7e"
+ },
+ "date": 1632256587677,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 13.8233,
+ "range": "±13.39%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.496.0.4960343"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 73.9023,
+ "range": "±16.34%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.496.0.4960343"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "c74e1b9e25531ce64bcfa805cbe5873f629f925a",
+ "message": "LUAFDN-607: Update MAX_PCALL_DEPTH usage",
+ "timestamp": "2021-09-21T22:08:34Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/201/commits/c74e1b9e25531ce64bcfa805cbe5873f629f925a"
+ },
+ "date": 1632346014827,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 13.1705,
+ "range": "±13.11%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.496.0.4960343"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 77.9703,
+ "range": "±21.09%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.496.0.4960343"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "5fe5615d45e448a94e57006eb8b8ce6822d85c19",
+ "message": "#nojira - Upgrade polyfill to play nice with in lua-apps",
+ "timestamp": "2021-09-23T16:01:31Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/202/commits/5fe5615d45e448a94e57006eb8b8ce6822d85c19"
+ },
+ "date": 1632425162831,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 12.5056,
+ "range": "±13.02%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.496.0.4960343"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 81.959,
+ "range": "±19.85%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.496.0.4960343"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "11f0938c4b71d4b3d1f11bf27333550cf4d105dd",
+ "message": "#nojira - Put warning behind dev",
+ "timestamp": "2021-09-23T19:52:04Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/203/commits/11f0938c4b71d4b3d1f11bf27333550cf4d105dd"
+ },
+ "date": 1632456049194,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 13.7101,
+ "range": "±12.71%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970357"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 74.4684,
+ "range": "±16.54%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970357"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "180e78d05c536fec1e9dee503db30a054db12af1",
+ "message": "#nojira - Update to rc.3",
+ "timestamp": "2021-09-24T04:08:39Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/204/commits/180e78d05c536fec1e9dee503db30a054db12af1"
+ },
+ "date": 1632508696225,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 14.3176,
+ "range": "±14.26%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970357"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 71.4084,
+ "range": "±16.05%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970357"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "6b2b75ac88f3405cbe2892693c8284665c074881",
+ "message": "Sometimes instance names are incorrect",
+ "timestamp": "2021-09-24T18:50:10Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/209/commits/6b2b75ac88f3405cbe2892693c8284665c074881"
+ },
+ "date": 1632769986549,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 15.0597,
+ "range": "±12.66%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970358"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 67.765,
+ "range": "±16.17%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970358"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "41a491696aa3933a35daf6c2b05903e3633c7d4c",
+ "message": "Made types compatible with Luau RFC recursive-type-restriction",
+ "timestamp": "2021-09-27T19:51:58Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/210/commits/41a491696aa3933a35daf6c2b05903e3633c7d4c"
+ },
+ "date": 1632775890915,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 15.2597,
+ "range": "±12.82%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 66.9929,
+ "range": "±18.05%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "8956b5705618418701bfd118411c4ac606e861b5",
+ "message": "LUAFDN-611: Fix hard runtime errors caused by multiple mistranslation bugs",
+ "timestamp": "2021-09-27T21:17:24Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/206/commits/8956b5705618418701bfd118411c4ac606e861b5"
+ },
+ "date": 1632777760952,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 13.1949,
+ "range": "±14.19%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 78.0315,
+ "range": "±21.54%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "8d1d0941fffd01703c6ae30208eb3979fad7c870",
+ "message": "LUAFDN-610: Fix a mistranslation bug that results in a hard runtime error",
+ "timestamp": "2021-09-27T21:35:46Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/205/commits/8d1d0941fffd01703c6ae30208eb3979fad7c870"
+ },
+ "date": 1632779126129,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 15.1513,
+ "range": "±14.99%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 67.8878,
+ "range": "±19.77%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "a45b813eba458f0757f081ca36c805c950628cd8",
+ "message": "LUAFDN-247: Type public API more thoroughly",
+ "timestamp": "2021-09-27T21:51:34Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/211/commits/a45b813eba458f0757f081ca36c805c950628cd8"
+ },
+ "date": 1632785098283,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 15.124,
+ "range": "±13.55%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 67.4266,
+ "range": "±14.78%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "6a24d389ce9e63933d437e3ee75ce6431db8959b",
+ "message": "Switch promise dependency to internal one",
+ "timestamp": "2021-09-27T23:52:53Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/212/commits/6a24d389ce9e63933d437e3ee75ce6431db8959b"
+ },
+ "date": 1632798147617,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 14.1983,
+ "range": "±12.22%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 71.5944,
+ "range": "±13.85%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "e7a58f4cfcc4a682fe9479c590b1455ca37c509d",
+ "message": "LUAFDN-498 - Better error output for bad prop assignments",
+ "timestamp": "2021-09-28T06:02:25Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/200/commits/e7a58f4cfcc4a682fe9479c590b1455ca37c509d"
+ },
+ "date": 1632872859491,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 14.9059,
+ "range": "±14.49%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 68.8521,
+ "range": "±19.61%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "c645f97ebb36682bb7e8d33d1b22a08b536ca18a",
+ "message": "#nojira Cleanup pragmas, add strict mode where reasonable, fix a bug",
+ "timestamp": "2021-09-29T00:15:19Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/213/commits/c645f97ebb36682bb7e8d33d1b22a08b536ca18a"
+ },
+ "date": 1632878705234,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 14.707,
+ "range": "±14.20%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 69.7532,
+ "range": "±18.48%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "737e2a29452e082b7398f2f4f561675309147df2",
+ "message": "LUAFDN-622 - Export `act` from the top level; enable it behind tests",
+ "timestamp": "2021-09-29T17:33:55Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/214/commits/737e2a29452e082b7398f2f4f561675309147df2"
+ },
+ "date": 1632961628087,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 13.1177,
+ "range": "±13.54%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 77.9108,
+ "range": "±16.83%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "1fbcbe16f83b5ee7a654c2ea835ceb7674722e32",
+ "message": "LUAFDN-58: Devtools quality improvements",
+ "timestamp": "2021-09-30T00:34:28Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/215/commits/1fbcbe16f83b5ee7a654c2ea835ceb7674722e32"
+ },
+ "date": 1632964059521,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 11.8442,
+ "range": "±15.44%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 86.7556,
+ "range": "±18.42%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "1a62c42d4dbb37c387e9647475a8f6bec33b2432",
+ "message": "LUAFDN-58: inject internals into dev tools if the global is present",
+ "timestamp": "2021-09-30T02:42:41Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/216/commits/1a62c42d4dbb37c387e9647475a8f6bec33b2432"
+ },
+ "date": 1633034432940,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 17.1488,
+ "range": "±13.80%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 59.6958,
+ "range": "±17.32%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "7edeedbcccb24cef1e506538d8df35ca2aedea45",
+ "message": "#nojira - Bump version",
+ "timestamp": "2021-09-30T20:42:34Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/217/commits/7edeedbcccb24cef1e506538d8df35ca2aedea45"
+ },
+ "date": 1633037737005,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 14.6637,
+ "range": "±12.81%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980396"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 69.6116,
+ "range": "±16.39%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980396"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "b1224be94e4b5d679e8c950e597953f813bc3adb",
+ "message": "RIDE-5275 React devtools inspector integration",
+ "timestamp": "2021-09-30T21:49:35Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/185/commits/b1224be94e4b5d679e8c950e597953f813bc3adb"
+ },
+ "date": 1633039268299,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 12.6705,
+ "range": "±13.69%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980396"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 80.7179,
+ "range": "±16.68%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980396"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "b28927f22559f567348efe7f0c63157baedf6a16",
+ "message": "#nojira - Switch promise to proper semver dep",
+ "timestamp": "2021-09-30T22:07:19Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/218/commits/b28927f22559f567348efe7f0c63157baedf6a16"
+ },
+ "date": 1633042758188,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 14.4817,
+ "range": "±12.55%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980396"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 70.486,
+ "range": "±16.51%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980396"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "3705df6efca1811e9cd025eaecabd2b49d034605",
+ "message": "#nojira - Strict mode seems to stumble sometimes when using the 'convert' command",
+ "timestamp": "2021-09-30T23:05:29Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/219/commits/3705df6efca1811e9cd025eaecabd2b49d034605"
+ },
+ "date": 1633109549553,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 14.9893,
+ "range": "±14.18%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980396"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 68.2713,
+ "range": "±16.73%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980396"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "b7a75292e97de978e822d69371f6f1a0435f6cf1",
+ "message": "Remove troublesome optimization",
+ "timestamp": "2021-10-01T17:39:47Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/221/commits/b7a75292e97de978e822d69371f6f1a0435f6cf1"
+ },
+ "date": 1633134827768,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 17.1401,
+ "range": "±12.53%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 59.5101,
+ "range": "±16.14%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "b76de6b4be3953f6b4da77b36e6d0d4a030609bc",
+ "message": "Adopt jest roblox with rotriever 0.5",
+ "timestamp": "2021-10-02T00:38:57Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/181/commits/b76de6b4be3953f6b4da77b36e6d0d4a030609bc"
+ },
+ "date": 1633358801110,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 12.8861,
+ "range": "±11.92%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 79.0098,
+ "range": "±15.24%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "aeb440bbb8474f606c94dc50ff4342ee76da2e5f",
+ "message": "LUAFDN-642",
+ "timestamp": "2021-10-04T14:53:49Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/223/commits/aeb440bbb8474f606c94dc50ff4342ee76da2e5f"
+ },
+ "date": 1633636748694,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 14.6377,
+ "range": "±15.04%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 70.0278,
+ "range": "±17.23%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "63272527248f7791e9aa6c28f975a563c2743af9",
+ "message": "LUAFDN-637 Split compatibility layer warnings",
+ "timestamp": "2021-10-07T20:50:49Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/224/commits/63272527248f7791e9aa6c28f975a563c2743af9"
+ },
+ "date": 1633715656402,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 16.5993,
+ "range": "±13.95%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 61.628,
+ "range": "±16.99%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "d77d7c6b84a4092ce2124bc2aa2ec77fe3693778",
+ "message": "#nojira Fix use*Effect create function type",
+ "timestamp": "2021-10-08T18:05:52Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/225/commits/d77d7c6b84a4092ce2124bc2aa2ec77fe3693778"
+ },
+ "date": 1633719577572,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 14.5196,
+ "range": "±14.65%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 70.5467,
+ "range": "±17.26%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "26008c76a91bb7eaa458b7ff756b20fd2f2a4522",
+ "message": "LUAFDN-580: Simple optimizations",
+ "timestamp": "2021-10-08T19:11:22Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/227/commits/26008c76a91bb7eaa458b7ff756b20fd2f2a4522"
+ },
+ "date": 1633733657044,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 16.8531,
+ "range": "±13.36%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 60.6722,
+ "range": "±17.15%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "a7f90fc02f826dd5240e57c4b351486b64404eab",
+ "message": "#nojira another fix that Alan's in-development analyze feature turned up",
+ "timestamp": "2021-10-08T23:23:58Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/228/commits/a7f90fc02f826dd5240e57c4b351486b64404eab"
+ },
+ "date": 1633738141136,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 13.1604,
+ "range": "±12.80%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 77.6299,
+ "range": "±17.09%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "fa677ba23ad2df4754d2721ae1fa2d2217973a8f",
+ "message": "#nojira - Bump version for rc.5",
+ "timestamp": "2021-10-09T00:15:48Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/226/commits/fa677ba23ad2df4754d2721ae1fa2d2217973a8f"
+ },
+ "date": 1633738825439,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 13.635,
+ "range": "±12.78%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 74.7593,
+ "range": "±15.32%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "bbbf7f1d1dcdaa33aebf99092913f23ee5829655",
+ "message": "robloxdev-cli convert can fail in strict mode",
+ "timestamp": "2021-10-09T01:49:01Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/229/commits/bbbf7f1d1dcdaa33aebf99092913f23ee5829655"
+ },
+ "date": 1633747702072,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 12.9613,
+ "range": "±12.44%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 78.5425,
+ "range": "±14.81%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "837d50d8d7f22fdcb28ec02b6e05919d2c50f057",
+ "message": "LUAFDN-623 - Use `act` in compat layer when the `__ROACT_17_INLINE_ACT__` global is enabled",
+ "timestamp": "2021-10-09T02:54:03Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/232/commits/837d50d8d7f22fdcb28ec02b6e05919d2c50f057"
+ },
+ "date": 1634056129479,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 14.4607,
+ "range": "±12.62%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 70.5386,
+ "range": "±15.96%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "733f87608ee9d26fe5d0e678a537b7840128a7a5",
+ "message": "LUAFDN-311: Use recycle pool for queue update objects",
+ "timestamp": "2021-10-12T16:50:10Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/231/commits/733f87608ee9d26fe5d0e678a537b7840128a7a5"
+ },
+ "date": 1634078781354,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 17.8031,
+ "range": "±14.49%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 57.6019,
+ "range": "±17.84%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "30a019de74755f03651f9d84ac961df1f7ce1d71",
+ "message": "#nojira - Add global for legacy root",
+ "timestamp": "2021-10-12T22:53:00Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/233/commits/30a019de74755f03651f9d84ac961df1f7ce1d71"
+ },
+ "date": 1634083022029,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 14.8933,
+ "range": "±11.99%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 68.2192,
+ "range": "±13.50%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "149598c92cfe72abc16c1b101903b1f65f95882b",
+ "message": "#nojira Work around new Luau recursive type error",
+ "timestamp": "2021-10-13T00:04:10Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/234/commits/149598c92cfe72abc16c1b101903b1f65f95882b"
+ },
+ "date": 1634092706115,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 13.6805,
+ "range": "±14.69%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 75.0156,
+ "range": "±18.32%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "a3fe1e8dd69f257f26fc29f589ae094fdc660a5d",
+ "message": "#nojira - Comment out object pool warning for now",
+ "timestamp": "2021-10-13T15:42:15Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/237/commits/a3fe1e8dd69f257f26fc29f589ae094fdc660a5d"
+ },
+ "date": 1634152386254,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 12.3315,
+ "range": "±14.03%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 82.8969,
+ "range": "±16.34%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "9c07840d01844b4ac2e044c0e5905a407bb08286",
+ "message": "LUAFDN-311: Moar simple optimizations",
+ "timestamp": "2021-10-13T19:45:06Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/230/commits/9c07840d01844b4ac2e044c0e5905a407bb08286"
+ },
+ "date": 1634154457866,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 15.3516,
+ "range": "±9.26%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 65.7393,
+ "range": "±10.11%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "4742d67758a17082104fc2bebc5e864d7f7e5055",
+ "message": "LUAFDN-311: Optimization pass",
+ "timestamp": "2021-10-13T19:56:31Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/220/commits/4742d67758a17082104fc2bebc5e864d7f7e5055"
+ },
+ "date": 1634161546613,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 14.7069,
+ "range": "±13.12%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.499.0.4990382"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 69.4098,
+ "range": "±16.04%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.499.0.4990382"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "cc4a52d1082b6b9dba96db3ca6cd8a9d98640536",
+ "message": "#nojira Update rotriever.toml for rc.6",
+ "timestamp": "2021-10-13T21:46:59Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/235/commits/cc4a52d1082b6b9dba96db3ca6cd8a9d98640536"
+ },
+ "date": 1634161825179,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 17.6136,
+ "range": "±12.64%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.499.0.4990382"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 57.9539,
+ "range": "±16.65%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.499.0.4990382"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "83a6aba3009e4c710e244395cb5cc65ae19351fa",
+ "message": "ci: update benchmarks workflow",
+ "timestamp": "2021-10-13T21:55:02Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/238/commits/83a6aba3009e4c710e244395cb5cc65ae19351fa"
+ },
+ "date": 1634750347984,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 14.1739,
+ "range": "±13.62%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 71.8198,
+ "range": "±13.56%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalInstructionsExecuted",
+ "value": 1128099654,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0030019972,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000672299,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryReads",
+ "value": 289350329,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0306789698,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0004797057,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryWrites",
+ "value": 145433583,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.012967947,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0068649206,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalInstructionsExecuted",
+ "value": 1356121126,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0034695256,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000600699,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryReads",
+ "value": 359122496,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0333761408,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0004223963,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryWrites",
+ "value": 173858268,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0126861956,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0068409804,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "2b8ebac9d357b22453c27aebc5c446932d49a6c7",
+ "message": "Change thresholds to udpate FPS branch",
+ "timestamp": "2021-10-28T18:43:06Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/242/commits/2b8ebac9d357b22453c27aebc5c446932d49a6c7"
+ },
+ "date": 1635446935715,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 12.3857,
+ "range": "±17.58%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 82.9941,
+ "range": "±16.90%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalInstructionsExecuted",
+ "value": 1124319424,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0029301966,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000658843,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryReads",
+ "value": 287464006,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0311022243,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0004394115,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryWrites",
+ "value": 143496630,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0132610362,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0073357123,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalInstructionsExecuted",
+ "value": 1348009180,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0033731321,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000591057,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryReads",
+ "value": 355949425,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0338158068,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0003927103,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryWrites",
+ "value": 171073085,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0128351283,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0069486208,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "f5858b79139430febb1f073f2182654db08bd637",
+ "message": "LUAFDN-694: eliminate unnecessary allocations",
+ "timestamp": "2021-10-28T19:24:12Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/241/commits/f5858b79139430febb1f073f2182654db08bd637"
+ },
+ "date": 1635543018931,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 13.0947,
+ "range": "±11.94%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 77.4486,
+ "range": "±12.17%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalInstructionsExecuted",
+ "value": 1130104092,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0031431459,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000646082,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryReads",
+ "value": 288891481,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0299911751,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0004207324,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryWrites",
+ "value": 142870629,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0132117218,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0070006411,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalInstructionsExecuted",
+ "value": 1390240142,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0035726468,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000538324,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryReads",
+ "value": 364703733,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0324562431,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataReadCacheMisses",
+ "value": 0.000366821,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryWrites",
+ "value": 173773021,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0127369657,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0068844289,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "15ddcfb84d3d69fe5f4937931a783ff1ec3df930",
+ "message": "Fix Mysterious top bar disappearing bug",
+ "timestamp": "2021-10-30T01:46:20Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/249/commits/15ddcfb84d3d69fe5f4937931a783ff1ec3df930"
+ },
+ "date": 1635830683977,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 15.4047,
+ "range": "±11.62%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 65.9128,
+ "range": "±13.46%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalInstructionsExecuted",
+ "value": 1125097636,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0031430526,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.000065669,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryReads",
+ "value": 287505572,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0304538828,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0004373654,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryWrites",
+ "value": 141884651,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0133041945,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0074204221,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalInstructionsExecuted",
+ "value": 1347154618,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0036069015,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000592605,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryReads",
+ "value": 355600557,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0332796189,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0003943526,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryWrites",
+ "value": 168605248,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0130121098,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0070414178,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "9d261c78bbe3ce612749e4e8823c918d4acf8c31",
+ "message": "LUAFDN-692: Add assertions around the instance map to detect bad objects being inserted",
+ "timestamp": "2021-11-02T16:35:50Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/248/commits/9d261c78bbe3ce612749e4e8823c918d4acf8c31"
+ },
+ "date": 1635971043655,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 14.2093,
+ "range": "±12.51%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 71.4694,
+ "range": "±12.94%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalInstructionsExecuted",
+ "value": 1150278142,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0030734306,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000707003,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryReads",
+ "value": 295424184,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0312771178,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0005019765,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryWrites",
+ "value": 143461876,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0132448359,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0073755135,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalInstructionsExecuted",
+ "value": 1395567461,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0034806508,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000560689,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryReads",
+ "value": 371091361,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0340695616,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0003968052,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryWrites",
+ "value": 171137669,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0128526292,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0069779319,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "f683410321e0b3eb3d4d893d1e3dd3692ef35e85",
+ "message": "LUAFDN-698: Fix possible memory leak in RobloxComponentProps.lua",
+ "timestamp": "2021-11-03T20:31:27Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/244/commits/f683410321e0b3eb3d4d893d1e3dd3692ef35e85"
+ },
+ "date": 1635977835677,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 12.3742,
+ "range": "±11.05%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 81.7781,
+ "range": "±11.15%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalInstructionsExecuted",
+ "value": 1152447215,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0030654263,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000670174,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryReads",
+ "value": 296137648,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0308926071,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0004735636,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryWrites",
+ "value": 143699390,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0131739112,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0074434902,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalInstructionsExecuted",
+ "value": 1402983025,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.003469233,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000565445,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryReads",
+ "value": 373063396,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0338572375,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0004024463,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryWrites",
+ "value": 172227811,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0128038264,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.006881496,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "eb240e688e5745b3493f682736278caf005dd4d0",
+ "message": "#nojira - Cut version rc.7",
+ "timestamp": "2021-11-04T15:57:57Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/251/commits/eb240e688e5745b3493f682736278caf005dd4d0"
+ },
+ "date": 1636041793270,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 17.9071,
+ "range": "±13.02%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 56.8063,
+ "range": "±13.81%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalInstructionsExecuted",
+ "value": 1154414010,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0030616789,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000651984,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryReads",
+ "value": 296773399,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0309590382,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0004644587,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryWrites",
+ "value": 143870332,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.01334118,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0074295373,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalInstructionsExecuted",
+ "value": 1400208066,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0034785066,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000540105,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryReads",
+ "value": 372335245,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0338501664,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0003730643,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryWrites",
+ "value": 171766050,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.012926431,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0069529223,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "353d64f2776e697b033af9735db7c32fc196255c",
+ "message": "#nojira Emergency fix to try and unblock the reverse merge",
+ "timestamp": "2021-11-04T16:32:04Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/253/commits/353d64f2776e697b033af9735db7c32fc196255c"
+ },
+ "date": 1636057640304,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 13.9541,
+ "range": "±12.05%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 72.6742,
+ "range": "±12.13%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalInstructionsExecuted",
+ "value": 1150895974,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0030721048,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000658713,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryReads",
+ "value": 295521189,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0304536471,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0004662373,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryWrites",
+ "value": 143464021,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0132269261,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0075124968,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalInstructionsExecuted",
+ "value": 1402186672,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0034715877,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000543651,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryReads",
+ "value": 372887766,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0333155392,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0003726483,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryWrites",
+ "value": 171891369,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0128247161,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0069434376,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "d4ea3c7eb4e9d35d3428b6c0e88ef2ac2acdf2af",
+ "message": "Fix __DISABLE_ALL_WARNINGS_EXCEPT_PROP_VALIDATION__",
+ "timestamp": "2021-11-04T20:38:54Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/255/commits/d4ea3c7eb4e9d35d3428b6c0e88ef2ac2acdf2af"
+ },
+ "date": 1636580041729,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 13.0434,
+ "range": "±10.20%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 77.3861,
+ "range": "±9.50%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalInstructionsExecuted",
+ "value": 1142193498,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0030007341,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000711447,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryReads",
+ "value": 293109727,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0305561371,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0005115763,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryWrites",
+ "value": 142848546,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0131880446,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0074749798,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalInstructionsExecuted",
+ "value": 1391964109,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0033939604,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000590683,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryReads",
+ "value": 370093719,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0333673023,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0004461465,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryWrites",
+ "value": 171156086,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0126943193,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0066576482,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "c0df797acc7f79c1e0928d1ac7ab884b156b34e5",
+ "message": "LUAFDN-723: Manually massage non-string errors into strings before re-throwing",
+ "timestamp": "2021-11-17T19:13:06Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/254/commits/c0df797acc7f79c1e0928d1ac7ab884b156b34e5"
+ },
+ "date": 1637179367035,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 14.1868,
+ "range": "±11.36%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 71.4005,
+ "range": "±11.56%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalInstructionsExecuted",
+ "value": 1139770750,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0030827401,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000664634,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryReads",
+ "value": 292204912,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0309972134,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0004763438,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryWrites",
+ "value": 142277253,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0132173201,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0074963353,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalInstructionsExecuted",
+ "value": 1393665520,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0034971404,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.000055006,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryReads",
+ "value": 370051025,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0338391739,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0003862197,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryWrites",
+ "value": 171173144,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0126859269,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0065535047,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "0408a7e730e0d979915f8e2cc362cdd6a3e2f65d",
+ "message": "Further optimizations",
+ "timestamp": "2021-11-17T20:11:34Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/240/commits/0408a7e730e0d979915f8e2cc362cdd6a3e2f65d"
+ },
+ "date": 1637191572804,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 13.5791,
+ "range": "±13.93%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 74.9015,
+ "range": "±13.02%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalInstructionsExecuted",
+ "value": 1127078657,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.003058641,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000656077,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryReads",
+ "value": 289108594,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0309931396,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0004394577,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryWrites",
+ "value": 141351901,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.013364815,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0074593691,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalInstructionsExecuted",
+ "value": 1370215803,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0034547208,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.000057969,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryReads",
+ "value": 364249296,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0338726392,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0003798388,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryWrites",
+ "value": 169086992,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0127355805,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0066514578,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "25e7dcd87c9987a2053ec0a52efc99460a024688",
+ "message": "Separate scheduler mocking from act inlining",
+ "timestamp": "2021-11-23T18:15:18Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/259/commits/25e7dcd87c9987a2053ec0a52efc99460a024688"
+ },
+ "date": 1638223876170,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 12.1633,
+ "range": "±13.84%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 83.9629,
+ "range": "±15.91%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalInstructionsExecuted",
+ "value": 1127693933,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0029830301,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000726518,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryReads",
+ "value": 289440585,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.030709864,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0002327248,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryWrites",
+ "value": 141492035,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0131291277,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0073423497,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalInstructionsExecuted",
+ "value": 1368476007,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0033661175,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000569882,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryReads",
+ "value": 364016342,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0337032479,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0001880108,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryWrites",
+ "value": 169192396,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0125774151,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0068694636,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "a8f8c4933fcae79ebfbd498723b680fb195be5c2",
+ "message": "LUAFDN-257: Backfilling tests",
+ "timestamp": "2021-11-29T23:50:37Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/258/commits/a8f8c4933fcae79ebfbd498723b680fb195be5c2"
+ },
+ "date": 1638303803799,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 13.3132,
+ "range": "±13.74%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 76.6184,
+ "range": "±15.33%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalInstructionsExecuted",
+ "value": 1128477590,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0029738721,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000724153,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryReads",
+ "value": 289689944,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0308350986,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0002320067,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryWrites",
+ "value": 141475872,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0130694158,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0073515716,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalInstructionsExecuted",
+ "value": 1370796218,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0033522116,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000595749,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryReads",
+ "value": 364724428,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0332761808,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0001960494,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryWrites",
+ "value": 169301367,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0125876597,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0069063412,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "ee62df85c6072dd919a664ffcde25cfac4367380",
+ "message": "#nojira - Provide properly-formatted args to cachegrind",
+ "timestamp": "2021-12-13T22:01:30Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/266/commits/ee62df85c6072dd919a664ffcde25cfac4367380"
+ },
+ "date": 1642635785511,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 17.0409,
+ "range": "±9.89%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 59.147,
+ "range": "±8.26%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalInstructionsExecuted",
+ "value": 1086859866,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0029935745,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000668835,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryReads",
+ "value": 279470528,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0312136777,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0001482231,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryWrites",
+ "value": 137062181,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0131309818,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0071235989,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalInstructionsExecuted",
+ "value": 1331575671,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0033909571,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000552271,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryReads",
+ "value": 354119537,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0342143732,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0001467329,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryWrites",
+ "value": 165533975,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.0127593927,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0071213115,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "ae436d269232d09c76caa2e70a48247d7f788d00",
+ "message": "LUAFDN-707 Fix useCallback hook in DEV mode",
+ "timestamp": "2021-12-13T22:01:30Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/269/commits/ae436d269232d09c76caa2e70a48247d7f788d00"
+ },
+ "date": 1645033057560,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 14.5337,
+ "range": "±13.35%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 69.7613,
+ "range": "±11.15%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalInstructionsExecuted",
+ "value": 1086486024,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0029265227,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000674744,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryReads",
+ "value": 279583012,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataReadCacheMisses",
+ "value": 0.0315628154,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataReadCacheMisses",
+ "value": 0.0001646595,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#TotalMemoryWrites",
+ "value": 137650832,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0132479621,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0076744324,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalInstructionsExecuted",
+ "value": 1332965079,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_InstrReadCacheMisses",
+ "value": 0.0032966325,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_InstrReadCacheMisses",
+ "value": 0.0000554043,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryReads",
+ "value": 354897136,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataReadCacheMisses",
+ "value": 0.0349867039,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataReadCacheMisses",
+ "value": 0.0001374849,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#TotalMemoryWrites",
+ "value": 166303316,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#L1_DataWriteCacheMisses",
+ "value": 0.012584241,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "MountWideTreeCG#LL_DataWriteCacheMisses",
+ "value": 0.0066895419,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ }
+ ]
+ }
+ ],
+ "Sierpinski Triangle Benchmark": [
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "f854288e5e7aa7f6f5bbfbbb962e8d28d672363f",
+ "message": "Calc bench on pr",
+ "timestamp": "2021-08-19T15:13:11Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/183/commits/f854288e5e7aa7f6f5bbfbbb962e8d28d672363f"
+ },
+ "date": 1629723306185,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#Δt",
+ "value": 11.3002,
+ "range": "±5.51%",
+ "unit": "ms/op",
+ "extra": "49 samples\nroblox-cli version: 0.492.0.428906"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "b05ed983ec8a2bfb8183ac394532cdf26fd396c4",
+ "message": "LUAFDN-496 Wrong Instance Names in Array of Children",
+ "timestamp": "2021-09-01T22:45:36Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/187/commits/b05ed983ec8a2bfb8183ac394532cdf26fd396c4"
+ },
+ "date": 1630616943115,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 102.9753,
+ "range": "±15.52%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.493.1.4930375"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 9.9321,
+ "range": "±14.53%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.493.1.4930375"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "0960cb8107c7599e71e7411d967065bc9fd8d9ad",
+ "message": "LUAFDN-540 Limit pcalls in recursivelyCommitLayoutEffects",
+ "timestamp": "2021-09-03T16:54:44Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/190/commits/0960cb8107c7599e71e7411d967065bc9fd8d9ad"
+ },
+ "date": 1630696319324,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 100.4906,
+ "range": "±11.55%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.494.0.4940337"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.0745,
+ "range": "±10.68%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.494.0.4940337"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "89a9413bf48d6f22096a6be5458202c7d6eafb24",
+ "message": "LUAFDN-317 React devtools",
+ "timestamp": "2021-09-03T19:53:44Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/115/commits/89a9413bf48d6f22096a6be5458202c7d6eafb24"
+ },
+ "date": 1630723700223,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 102.3565,
+ "range": "±13.63%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.494.0.4940339"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 9.9553,
+ "range": "±13.91%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.494.0.4940339"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "d316007283b3ec76aabda8cc9fc6e6f847afc937",
+ "message": "LUAFDN-537 and LUAFDN-499: fix string formatting in several error messages",
+ "timestamp": "2021-09-04T02:58:11Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/192/commits/d316007283b3ec76aabda8cc9fc6e6f847afc937"
+ },
+ "date": 1631297859445,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 100.5119,
+ "range": "±9.30%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.495.0.4950265"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.0323,
+ "range": "±8.96%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.495.0.4950265"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "cdad9e14ed240e308df8ed82ea6913545d5c14da",
+ "message": "LUAFDN-56: Debug tracing API and tests",
+ "timestamp": "2021-09-10T18:47:41Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/143/commits/cdad9e14ed240e308df8ed82ea6913545d5c14da"
+ },
+ "date": 1631306412071,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 88.1114,
+ "range": "±12.30%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.495.0.4950265"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 11.5328,
+ "range": "±13.20%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.495.0.4950265"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "f6a2e9ae2764afcfd9958c8aa4cc9bea68f68c81",
+ "message": "Switch back to setTimeout",
+ "timestamp": "2021-09-10T23:31:56Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/195/commits/f6a2e9ae2764afcfd9958c8aa4cc9bea68f68c81"
+ },
+ "date": 1631648271877,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 95.2944,
+ "range": "±10.99%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.495.0.4950267"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.6152,
+ "range": "±10.44%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.495.0.4950267"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "47dcf2451a484650001d667ff4ee068f2254ccb9",
+ "message": "LUAFDN-58 Fix additional bugs/quality gaps in DevTools",
+ "timestamp": "2021-09-14T21:12:42Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/197/commits/47dcf2451a484650001d667ff4ee068f2254ccb9"
+ },
+ "date": 1631844122877,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 87.0444,
+ "range": "±17.30%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.496.0.4960343"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 11.8321,
+ "range": "±17.24%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.496.0.4960343"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "59d9aee9aa9fbaa9573632fc4bf68072d81aae7e",
+ "message": "LUAFDN-538: use task library",
+ "timestamp": "2021-09-17T02:14:27Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/198/commits/59d9aee9aa9fbaa9573632fc4bf68072d81aae7e"
+ },
+ "date": 1632256588398,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 100.9674,
+ "range": "±13.87%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.496.0.4960343"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.132,
+ "range": "±16.69%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.496.0.4960343"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "c74e1b9e25531ce64bcfa805cbe5873f629f925a",
+ "message": "LUAFDN-607: Update MAX_PCALL_DEPTH usage",
+ "timestamp": "2021-09-21T22:08:34Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/201/commits/c74e1b9e25531ce64bcfa805cbe5873f629f925a"
+ },
+ "date": 1632346015560,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 85.6057,
+ "range": "±9.81%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.496.0.4960343"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 11.7991,
+ "range": "±10.23%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.496.0.4960343"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "5fe5615d45e448a94e57006eb8b8ce6822d85c19",
+ "message": "#nojira - Upgrade polyfill to play nice with in lua-apps",
+ "timestamp": "2021-09-23T16:01:31Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/202/commits/5fe5615d45e448a94e57006eb8b8ce6822d85c19"
+ },
+ "date": 1632425163701,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 87.6227,
+ "range": "±10.96%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.496.0.4960343"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 11.5552,
+ "range": "±11.37%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.496.0.4960343"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "11f0938c4b71d4b3d1f11bf27333550cf4d105dd",
+ "message": "#nojira - Put warning behind dev",
+ "timestamp": "2021-09-23T19:52:04Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/203/commits/11f0938c4b71d4b3d1f11bf27333550cf4d105dd"
+ },
+ "date": 1632456049866,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 86.2582,
+ "range": "±11.38%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970357"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 11.747,
+ "range": "±11.65%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970357"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "180e78d05c536fec1e9dee503db30a054db12af1",
+ "message": "#nojira - Update to rc.3",
+ "timestamp": "2021-09-24T04:08:39Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/204/commits/180e78d05c536fec1e9dee503db30a054db12af1"
+ },
+ "date": 1632508696893,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 97.4152,
+ "range": "±13.13%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970357"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.4496,
+ "range": "±13.55%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970357"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "6b2b75ac88f3405cbe2892693c8284665c074881",
+ "message": "Sometimes instance names are incorrect",
+ "timestamp": "2021-09-24T18:50:10Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/209/commits/6b2b75ac88f3405cbe2892693c8284665c074881"
+ },
+ "date": 1632769987145,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 94.811,
+ "range": "±12.60%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970358"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.7441,
+ "range": "±14.72%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970358"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "41a491696aa3933a35daf6c2b05903e3633c7d4c",
+ "message": "Made types compatible with Luau RFC recursive-type-restriction",
+ "timestamp": "2021-09-27T19:51:58Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/210/commits/41a491696aa3933a35daf6c2b05903e3633c7d4c"
+ },
+ "date": 1632775891435,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 94.965,
+ "range": "±12.08%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.6988,
+ "range": "±13.22%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "8956b5705618418701bfd118411c4ac606e861b5",
+ "message": "LUAFDN-611: Fix hard runtime errors caused by multiple mistranslation bugs",
+ "timestamp": "2021-09-27T21:17:24Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/206/commits/8956b5705618418701bfd118411c4ac606e861b5"
+ },
+ "date": 1632777761673,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 77.3473,
+ "range": "±12.53%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 13.1313,
+ "range": "±12.49%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "8d1d0941fffd01703c6ae30208eb3979fad7c870",
+ "message": "LUAFDN-610: Fix a mistranslation bug that results in a hard runtime error",
+ "timestamp": "2021-09-27T21:35:46Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/205/commits/8d1d0941fffd01703c6ae30208eb3979fad7c870"
+ },
+ "date": 1632779126758,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 98.6196,
+ "range": "±13.54%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.3607,
+ "range": "±16.41%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "a45b813eba458f0757f081ca36c805c950628cd8",
+ "message": "LUAFDN-247: Type public API more thoroughly",
+ "timestamp": "2021-09-27T21:51:34Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/211/commits/a45b813eba458f0757f081ca36c805c950628cd8"
+ },
+ "date": 1632785098815,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 109.4538,
+ "range": "±10.80%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 9.251,
+ "range": "±11.68%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "6a24d389ce9e63933d437e3ee75ce6431db8959b",
+ "message": "Switch promise dependency to internal one",
+ "timestamp": "2021-09-27T23:52:53Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/212/commits/6a24d389ce9e63933d437e3ee75ce6431db8959b"
+ },
+ "date": 1632798148275,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 93.5237,
+ "range": "±8.98%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.7819,
+ "range": "±9.28%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "e7a58f4cfcc4a682fe9479c590b1455ca37c509d",
+ "message": "LUAFDN-498 - Better error output for bad prop assignments",
+ "timestamp": "2021-09-28T06:02:25Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/200/commits/e7a58f4cfcc4a682fe9479c590b1455ca37c509d"
+ },
+ "date": 1632872860112,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 105.6557,
+ "range": "±9.57%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 9.5539,
+ "range": "±9.82%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "c645f97ebb36682bb7e8d33d1b22a08b536ca18a",
+ "message": "#nojira Cleanup pragmas, add strict mode where reasonable, fix a bug",
+ "timestamp": "2021-09-29T00:15:19Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/213/commits/c645f97ebb36682bb7e8d33d1b22a08b536ca18a"
+ },
+ "date": 1632878708039,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 96.7296,
+ "range": "±15.78%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.6381,
+ "range": "±18.89%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "737e2a29452e082b7398f2f4f561675309147df2",
+ "message": "LUAFDN-622 - Export `act` from the top level; enable it behind tests",
+ "timestamp": "2021-09-29T17:33:55Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/214/commits/737e2a29452e082b7398f2f4f561675309147df2"
+ },
+ "date": 1632961628954,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 94.3201,
+ "range": "±11.51%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.7507,
+ "range": "±12.14%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "1fbcbe16f83b5ee7a654c2ea835ceb7674722e32",
+ "message": "LUAFDN-58: Devtools quality improvements",
+ "timestamp": "2021-09-30T00:34:28Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/215/commits/1fbcbe16f83b5ee7a654c2ea835ceb7674722e32"
+ },
+ "date": 1632964060115,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 88.6791,
+ "range": "±17.11%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 11.6428,
+ "range": "±18.83%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "1a62c42d4dbb37c387e9647475a8f6bec33b2432",
+ "message": "LUAFDN-58: inject internals into dev tools if the global is present",
+ "timestamp": "2021-09-30T02:42:41Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/216/commits/1a62c42d4dbb37c387e9647475a8f6bec33b2432"
+ },
+ "date": 1633034433612,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 103.7986,
+ "range": "±11.44%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 9.7624,
+ "range": "±11.63%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.497.0.4970359"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "7edeedbcccb24cef1e506538d8df35ca2aedea45",
+ "message": "#nojira - Bump version",
+ "timestamp": "2021-09-30T20:42:34Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/217/commits/7edeedbcccb24cef1e506538d8df35ca2aedea45"
+ },
+ "date": 1633037737569,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 92.4191,
+ "range": "±9.85%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980396"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.9262,
+ "range": "±9.92%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980396"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "b1224be94e4b5d679e8c950e597953f813bc3adb",
+ "message": "RIDE-5275 React devtools inspector integration",
+ "timestamp": "2021-09-30T21:49:35Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/185/commits/b1224be94e4b5d679e8c950e597953f813bc3adb"
+ },
+ "date": 1633039269128,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 82.7136,
+ "range": "±12.09%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980396"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 12.2622,
+ "range": "±11.75%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980396"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "b28927f22559f567348efe7f0c63157baedf6a16",
+ "message": "#nojira - Switch promise to proper semver dep",
+ "timestamp": "2021-09-30T22:07:19Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/218/commits/b28927f22559f567348efe7f0c63157baedf6a16"
+ },
+ "date": 1633042758749,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 88.6473,
+ "range": "±11.48%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980396"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 11.4661,
+ "range": "±14.63%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980396"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "3705df6efca1811e9cd025eaecabd2b49d034605",
+ "message": "#nojira - Strict mode seems to stumble sometimes when using the 'convert' command",
+ "timestamp": "2021-09-30T23:05:29Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/219/commits/3705df6efca1811e9cd025eaecabd2b49d034605"
+ },
+ "date": 1633109550227,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 114.9687,
+ "range": "±12.70%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980396"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 8.8524,
+ "range": "±13.99%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980396"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "b7a75292e97de978e822d69371f6f1a0435f6cf1",
+ "message": "Remove troublesome optimization",
+ "timestamp": "2021-10-01T17:39:47Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/221/commits/b7a75292e97de978e822d69371f6f1a0435f6cf1"
+ },
+ "date": 1633134828373,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 108.7327,
+ "range": "±9.51%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 9.2835,
+ "range": "±9.88%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "b76de6b4be3953f6b4da77b36e6d0d4a030609bc",
+ "message": "Adopt jest roblox with rotriever 0.5",
+ "timestamp": "2021-10-02T00:38:57Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/181/commits/b76de6b4be3953f6b4da77b36e6d0d4a030609bc"
+ },
+ "date": 1633358802098,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 93.4441,
+ "range": "±9.58%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.8052,
+ "range": "±10.11%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "aeb440bbb8474f606c94dc50ff4342ee76da2e5f",
+ "message": "LUAFDN-642",
+ "timestamp": "2021-10-04T14:53:49Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/223/commits/aeb440bbb8474f606c94dc50ff4342ee76da2e5f"
+ },
+ "date": 1633636749393,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 110.9846,
+ "range": "±9.33%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 9.0889,
+ "range": "±9.40%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "63272527248f7791e9aa6c28f975a563c2743af9",
+ "message": "LUAFDN-637 Split compatibility layer warnings",
+ "timestamp": "2021-10-07T20:50:49Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/224/commits/63272527248f7791e9aa6c28f975a563c2743af9"
+ },
+ "date": 1633715657098,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 114.8182,
+ "range": "±11.63%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 8.8265,
+ "range": "±11.51%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "d77d7c6b84a4092ce2124bc2aa2ec77fe3693778",
+ "message": "#nojira Fix use*Effect create function type",
+ "timestamp": "2021-10-08T18:05:52Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/225/commits/d77d7c6b84a4092ce2124bc2aa2ec77fe3693778"
+ },
+ "date": 1633719578290,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 108.0228,
+ "range": "±10.24%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 9.3552,
+ "range": "±10.31%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "26008c76a91bb7eaa458b7ff756b20fd2f2a4522",
+ "message": "LUAFDN-580: Simple optimizations",
+ "timestamp": "2021-10-08T19:11:22Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/227/commits/26008c76a91bb7eaa458b7ff756b20fd2f2a4522"
+ },
+ "date": 1633733657701,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 110.8305,
+ "range": "±9.33%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 9.1028,
+ "range": "±9.49%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "a7f90fc02f826dd5240e57c4b351486b64404eab",
+ "message": "#nojira another fix that Alan's in-development analyze feature turned up",
+ "timestamp": "2021-10-08T23:23:58Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/228/commits/a7f90fc02f826dd5240e57c4b351486b64404eab"
+ },
+ "date": 1633738141909,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 97.9342,
+ "range": "±11.55%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.3541,
+ "range": "±12.11%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "fa677ba23ad2df4754d2721ae1fa2d2217973a8f",
+ "message": "#nojira - Bump version for rc.5",
+ "timestamp": "2021-10-09T00:15:48Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/226/commits/fa677ba23ad2df4754d2721ae1fa2d2217973a8f"
+ },
+ "date": 1633738826068,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 84.7773,
+ "range": "±12.48%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 11.9904,
+ "range": "±13.20%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "bbbf7f1d1dcdaa33aebf99092913f23ee5829655",
+ "message": "robloxdev-cli convert can fail in strict mode",
+ "timestamp": "2021-10-09T01:49:01Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/229/commits/bbbf7f1d1dcdaa33aebf99092913f23ee5829655"
+ },
+ "date": 1633747702664,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 92.4659,
+ "range": "±13.35%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 11.0154,
+ "range": "±13.83%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "837d50d8d7f22fdcb28ec02b6e05919d2c50f057",
+ "message": "LUAFDN-623 - Use `act` in compat layer when the `__ROACT_17_INLINE_ACT__` global is enabled",
+ "timestamp": "2021-10-09T02:54:03Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/232/commits/837d50d8d7f22fdcb28ec02b6e05919d2c50f057"
+ },
+ "date": 1634056130205,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 91.1503,
+ "range": "±11.16%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 11.1085,
+ "range": "±11.19%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "733f87608ee9d26fe5d0e678a537b7840128a7a5",
+ "message": "LUAFDN-311: Use recycle pool for queue update objects",
+ "timestamp": "2021-10-12T16:50:10Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/231/commits/733f87608ee9d26fe5d0e678a537b7840128a7a5"
+ },
+ "date": 1634078782006,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 107.9921,
+ "range": "±16.20%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 9.535,
+ "range": "±18.35%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "30a019de74755f03651f9d84ac961df1f7ce1d71",
+ "message": "#nojira - Add global for legacy root",
+ "timestamp": "2021-10-12T22:53:00Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/233/commits/30a019de74755f03651f9d84ac961df1f7ce1d71"
+ },
+ "date": 1634083022735,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 91.8054,
+ "range": "±11.83%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 11.0592,
+ "range": "±12.94%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "149598c92cfe72abc16c1b101903b1f65f95882b",
+ "message": "#nojira Work around new Luau recursive type error",
+ "timestamp": "2021-10-13T00:04:10Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/234/commits/149598c92cfe72abc16c1b101903b1f65f95882b"
+ },
+ "date": 1634092706783,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 98.7981,
+ "range": "±10.20%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.2301,
+ "range": "±10.51%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "a3fe1e8dd69f257f26fc29f589ae094fdc660a5d",
+ "message": "#nojira - Comment out object pool warning for now",
+ "timestamp": "2021-10-13T15:42:15Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/237/commits/a3fe1e8dd69f257f26fc29f589ae094fdc660a5d"
+ },
+ "date": 1634152387014,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 83.0522,
+ "range": "±10.38%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 12.1658,
+ "range": "±9.98%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "9c07840d01844b4ac2e044c0e5905a407bb08286",
+ "message": "LUAFDN-311: Moar simple optimizations",
+ "timestamp": "2021-10-13T19:45:06Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/230/commits/9c07840d01844b4ac2e044c0e5905a407bb08286"
+ },
+ "date": 1634154458477,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 98.4747,
+ "range": "±12.80%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.3292,
+ "range": "±13.44%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.498.0.4980398"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "4742d67758a17082104fc2bebc5e864d7f7e5055",
+ "message": "LUAFDN-311: Optimization pass",
+ "timestamp": "2021-10-13T19:56:31Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/220/commits/4742d67758a17082104fc2bebc5e864d7f7e5055"
+ },
+ "date": 1634161547232,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 90.9743,
+ "range": "±17.18%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.499.0.4990382"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 11.3545,
+ "range": "±19.46%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.499.0.4990382"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "cc4a52d1082b6b9dba96db3ca6cd8a9d98640536",
+ "message": "#nojira Update rotriever.toml for rc.6",
+ "timestamp": "2021-10-13T21:46:59Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/235/commits/cc4a52d1082b6b9dba96db3ca6cd8a9d98640536"
+ },
+ "date": 1634161825808,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 111.2714,
+ "range": "±12.91%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.499.0.4990382"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 9.1306,
+ "range": "±12.29%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.499.0.4990382"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "83a6aba3009e4c710e244395cb5cc65ae19351fa",
+ "message": "ci: update benchmarks workflow",
+ "timestamp": "2021-10-13T21:55:02Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/238/commits/83a6aba3009e4c710e244395cb5cc65ae19351fa"
+ },
+ "date": 1634750348128,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 110.4384,
+ "range": "±14.04%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 9.2286,
+ "range": "±13.56%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalInstructionsExecuted",
+ "value": 813209878,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0025572919,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000955067,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryReads",
+ "value": 193876972,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataReadCacheMisses",
+ "value": 0.0248137412,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataReadCacheMisses",
+ "value": 0.000692532,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryWrites",
+ "value": 107852636,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0139138185,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0084221956,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalInstructionsExecuted",
+ "value": 863680117,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_InstrReadCacheMisses",
+ "value": 0.0025077016,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_InstrReadCacheMisses",
+ "value": 0.0000907385,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryReads",
+ "value": 209197643,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataReadCacheMisses",
+ "value": 0.0256858439,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataReadCacheMisses",
+ "value": 0.0006438409,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryWrites",
+ "value": 113831166,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataWriteCacheMisses",
+ "value": 0.013516966,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataWriteCacheMisses",
+ "value": 0.0080468121,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "2b8ebac9d357b22453c27aebc5c446932d49a6c7",
+ "message": "Change thresholds to udpate FPS branch",
+ "timestamp": "2021-10-28T18:43:06Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/242/commits/2b8ebac9d357b22453c27aebc5c446932d49a6c7"
+ },
+ "date": 1635446935875,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 101.4379,
+ "range": "±12.63%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.012,
+ "range": "±12.30%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalInstructionsExecuted",
+ "value": 818307642,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_InstrReadCacheMisses",
+ "value": 0.002604182,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000945891,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryReads",
+ "value": 194653300,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataReadCacheMisses",
+ "value": 0.0256019343,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataReadCacheMisses",
+ "value": 0.0007155954,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryWrites",
+ "value": 107708227,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0140865841,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0084392718,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalInstructionsExecuted",
+ "value": 847830875,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_InstrReadCacheMisses",
+ "value": 0.0024872425,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_InstrReadCacheMisses",
+ "value": 0.0000913,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryReads",
+ "value": 203875429,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataReadCacheMisses",
+ "value": 0.026124899,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataReadCacheMisses",
+ "value": 0.0006827355,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryWrites",
+ "value": 110243562,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataWriteCacheMisses",
+ "value": 0.0139525699,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataWriteCacheMisses",
+ "value": 0.0082982896,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "f5858b79139430febb1f073f2182654db08bd637",
+ "message": "LUAFDN-694: eliminate unnecessary allocations",
+ "timestamp": "2021-10-28T19:24:12Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/241/commits/f5858b79139430febb1f073f2182654db08bd637"
+ },
+ "date": 1635543019143,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 113.0314,
+ "range": "±11.13%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 8.9522,
+ "range": "±10.64%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalInstructionsExecuted",
+ "value": 814685092,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0026486443,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000914893,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryReads",
+ "value": 193277425,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataReadCacheMisses",
+ "value": 0.0248283885,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataReadCacheMisses",
+ "value": 0.0006990987,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryWrites",
+ "value": 106626905,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0140953918,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0079710745,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalInstructionsExecuted",
+ "value": 854149410,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_InstrReadCacheMisses",
+ "value": 0.0025617321,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_InstrReadCacheMisses",
+ "value": 0.0000879354,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryReads",
+ "value": 205478044,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataReadCacheMisses",
+ "value": 0.0262604018,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataReadCacheMisses",
+ "value": 0.0006617836,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryWrites",
+ "value": 110335950,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataWriteCacheMisses",
+ "value": 0.0139449472,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataWriteCacheMisses",
+ "value": 0.0082669882,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "15ddcfb84d3d69fe5f4937931a783ff1ec3df930",
+ "message": "Fix Mysterious top bar disappearing bug",
+ "timestamp": "2021-10-30T01:46:20Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/249/commits/15ddcfb84d3d69fe5f4937931a783ff1ec3df930"
+ },
+ "date": 1635830684161,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 100.126,
+ "range": "±15.36%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.2427,
+ "range": "±16.83%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalInstructionsExecuted",
+ "value": 817080364,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0026764528,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000882741,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryReads",
+ "value": 194273271,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataReadCacheMisses",
+ "value": 0.0256439395,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataReadCacheMisses",
+ "value": 0.0006236988,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryWrites",
+ "value": 106760150,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0142370257,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0084952297,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalInstructionsExecuted",
+ "value": 846419261,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_InstrReadCacheMisses",
+ "value": 0.0025650822,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_InstrReadCacheMisses",
+ "value": 0.0000852603,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryReads",
+ "value": 203417614,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataReadCacheMisses",
+ "value": 0.0259395777,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataReadCacheMisses",
+ "value": 0.0005810805,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryWrites",
+ "value": 109169689,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataWriteCacheMisses",
+ "value": 0.0141217129,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataWriteCacheMisses",
+ "value": 0.0077661025,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "9d261c78bbe3ce612749e4e8823c918d4acf8c31",
+ "message": "LUAFDN-692: Add assertions around the instance map to detect bad objects being inserted",
+ "timestamp": "2021-11-02T16:35:50Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/248/commits/9d261c78bbe3ce612749e4e8823c918d4acf8c31"
+ },
+ "date": 1635971043828,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 94.9366,
+ "range": "±13.25%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.7199,
+ "range": "±13.34%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalInstructionsExecuted",
+ "value": 816698414,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0026819717,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000883398,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryReads",
+ "value": 194101849,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataReadCacheMisses",
+ "value": 0.025406945,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataReadCacheMisses",
+ "value": 0.0006238838,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryWrites",
+ "value": 106740241,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0142652198,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0085030631,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalInstructionsExecuted",
+ "value": 846064412,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_InstrReadCacheMisses",
+ "value": 0.0025645943,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_InstrReadCacheMisses",
+ "value": 0.0000852831,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryReads",
+ "value": 203319224,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataReadCacheMisses",
+ "value": 0.026172867,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataReadCacheMisses",
+ "value": 0.0005813813,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryWrites",
+ "value": 109012014,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataWriteCacheMisses",
+ "value": 0.0141007302,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataWriteCacheMisses",
+ "value": 0.0077773997,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "f683410321e0b3eb3d4d893d1e3dd3692ef35e85",
+ "message": "LUAFDN-698: Fix possible memory leak in RobloxComponentProps.lua",
+ "timestamp": "2021-11-03T20:31:27Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/244/commits/f683410321e0b3eb3d4d893d1e3dd3692ef35e85"
+ },
+ "date": 1635977835869,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 108.5237,
+ "range": "±12.56%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 9.3568,
+ "range": "±12.21%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalInstructionsExecuted",
+ "value": 809726308,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0026435513,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_InstrReadCacheMisses",
+ "value": 0.000092183,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryReads",
+ "value": 192142111,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataReadCacheMisses",
+ "value": 0.0251211459,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataReadCacheMisses",
+ "value": 0.0007070184,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryWrites",
+ "value": 105596879,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataWriteCacheMisses",
+ "value": 0.014209795,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0086016273,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalInstructionsExecuted",
+ "value": 864735057,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_InstrReadCacheMisses",
+ "value": 0.0025886461,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_InstrReadCacheMisses",
+ "value": 0.0000901247,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryReads",
+ "value": 208831749,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataReadCacheMisses",
+ "value": 0.0266201142,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataReadCacheMisses",
+ "value": 0.0006508062,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryWrites",
+ "value": 111803334,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataWriteCacheMisses",
+ "value": 0.0140213439,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataWriteCacheMisses",
+ "value": 0.0081922065,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "eb240e688e5745b3493f682736278caf005dd4d0",
+ "message": "#nojira - Cut version rc.7",
+ "timestamp": "2021-11-04T15:57:57Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/251/commits/eb240e688e5745b3493f682736278caf005dd4d0"
+ },
+ "date": 1636041793467,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 96.3736,
+ "range": "±13.84%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.5865,
+ "range": "±14.57%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalInstructionsExecuted",
+ "value": 819347735,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0026770856,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000897592,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryReads",
+ "value": 194791676,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataReadCacheMisses",
+ "value": 0.0254215586,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataReadCacheMisses",
+ "value": 0.0006632984,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryWrites",
+ "value": 107020571,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0141896272,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0078704215,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalInstructionsExecuted",
+ "value": 850676744,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_InstrReadCacheMisses",
+ "value": 0.0025674359,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_InstrReadCacheMisses",
+ "value": 0.0000898555,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryReads",
+ "value": 204508979,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataReadCacheMisses",
+ "value": 0.0261506758,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataReadCacheMisses",
+ "value": 0.0006270776,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryWrites",
+ "value": 109667014,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataWriteCacheMisses",
+ "value": 0.0141057638,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataWriteCacheMisses",
+ "value": 0.0077442612,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "353d64f2776e697b033af9735db7c32fc196255c",
+ "message": "#nojira Emergency fix to try and unblock the reverse merge",
+ "timestamp": "2021-11-04T16:32:04Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/253/commits/353d64f2776e697b033af9735db7c32fc196255c"
+ },
+ "date": 1636057640503,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 97.8945,
+ "range": "±16.83%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.598,
+ "range": "±22.91%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalInstructionsExecuted",
+ "value": 819082133,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_InstrReadCacheMisses",
+ "value": 0.002679376,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000900032,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryReads",
+ "value": 194786816,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataReadCacheMisses",
+ "value": 0.0256234539,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataReadCacheMisses",
+ "value": 0.0006616156,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryWrites",
+ "value": 107021341,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0142268167,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0078712899,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalInstructionsExecuted",
+ "value": 850192886,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_InstrReadCacheMisses",
+ "value": 0.0025556248,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_InstrReadCacheMisses",
+ "value": 0.0000871332,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryReads",
+ "value": 204500703,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataReadCacheMisses",
+ "value": 0.0262417435,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataReadCacheMisses",
+ "value": 0.0006330394,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryWrites",
+ "value": 109598551,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataWriteCacheMisses",
+ "value": 0.0140689269,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataWriteCacheMisses",
+ "value": 0.0077450933,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "d4ea3c7eb4e9d35d3428b6c0e88ef2ac2acdf2af",
+ "message": "Fix __DISABLE_ALL_WARNINGS_EXCEPT_PROP_VALIDATION__",
+ "timestamp": "2021-11-04T20:38:54Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/255/commits/d4ea3c7eb4e9d35d3428b6c0e88ef2ac2acdf2af"
+ },
+ "date": 1636580041941,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 96.9159,
+ "range": "±16.23%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.6262,
+ "range": "±18.47%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalInstructionsExecuted",
+ "value": 809214486,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0026377877,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000903098,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryReads",
+ "value": 191997048,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataReadCacheMisses",
+ "value": 0.0250412861,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataReadCacheMisses",
+ "value": 0.0006743906,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryWrites",
+ "value": 106030523,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0141936865,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0079529929,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalInstructionsExecuted",
+ "value": 843042916,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_InstrReadCacheMisses",
+ "value": 0.0025224339,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_InstrReadCacheMisses",
+ "value": 0.0000891295,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryReads",
+ "value": 202529108,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataReadCacheMisses",
+ "value": 0.0258065226,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataReadCacheMisses",
+ "value": 0.0006776557,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryWrites",
+ "value": 108747571,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataWriteCacheMisses",
+ "value": 0.0139196672,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataWriteCacheMisses",
+ "value": 0.0083976956,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "c0df797acc7f79c1e0928d1ac7ab884b156b34e5",
+ "message": "LUAFDN-723: Manually massage non-string errors into strings before re-throwing",
+ "timestamp": "2021-11-17T19:13:06Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/254/commits/c0df797acc7f79c1e0928d1ac7ab884b156b34e5"
+ },
+ "date": 1637179367237,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 92.556,
+ "range": "±12.82%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 10.9828,
+ "range": "±12.88%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalInstructionsExecuted",
+ "value": 812200315,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0026660332,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000908704,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryReads",
+ "value": 192762398,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataReadCacheMisses",
+ "value": 0.0253656732,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataReadCacheMisses",
+ "value": 0.0006948191,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryWrites",
+ "value": 106215350,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0141445657,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0079289858,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalInstructionsExecuted",
+ "value": 842813834,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_InstrReadCacheMisses",
+ "value": 0.0025463441,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_InstrReadCacheMisses",
+ "value": 0.0000926848,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryReads",
+ "value": 202306672,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataReadCacheMisses",
+ "value": 0.026039769,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataReadCacheMisses",
+ "value": 0.0006751779,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryWrites",
+ "value": 108766444,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataWriteCacheMisses",
+ "value": 0.0140804548,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataWriteCacheMisses",
+ "value": 0.0083995207,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "0408a7e730e0d979915f8e2cc362cdd6a3e2f65d",
+ "message": "Further optimizations",
+ "timestamp": "2021-11-17T20:11:34Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/240/commits/0408a7e730e0d979915f8e2cc362cdd6a3e2f65d"
+ },
+ "date": 1637191573002,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 121.5116,
+ "range": "±16.02%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 8.4388,
+ "range": "±15.66%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalInstructionsExecuted",
+ "value": 813119913,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0027386465,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000883191,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryReads",
+ "value": 193103044,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataReadCacheMisses",
+ "value": 0.0253617804,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataReadCacheMisses",
+ "value": 0.0006182968,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryWrites",
+ "value": 106914860,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0141743159,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0078820662,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalInstructionsExecuted",
+ "value": 859145686,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_InstrReadCacheMisses",
+ "value": 0.002710091,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_InstrReadCacheMisses",
+ "value": 0.0000838368,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryReads",
+ "value": 207050096,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataReadCacheMisses",
+ "value": 0.0264632671,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataReadCacheMisses",
+ "value": 0.000591311,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryWrites",
+ "value": 112192744,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataWriteCacheMisses",
+ "value": 0.0138498885,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataWriteCacheMisses",
+ "value": 0.0081461596,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "25e7dcd87c9987a2053ec0a52efc99460a024688",
+ "message": "Separate scheduler mocking from act inlining",
+ "timestamp": "2021-11-23T18:15:18Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/259/commits/25e7dcd87c9987a2053ec0a52efc99460a024688"
+ },
+ "date": 1638223876372,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 124.5411,
+ "range": "±12.93%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 8.1603,
+ "range": "±12.62%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalInstructionsExecuted",
+ "value": 818490377,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0028084753,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000878679,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryReads",
+ "value": 194742578,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataReadCacheMisses",
+ "value": 0.0252045241,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataReadCacheMisses",
+ "value": 0.0001958123,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryWrites",
+ "value": 108072216,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0138078782,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0075769983,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalInstructionsExecuted",
+ "value": 850721900,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_InstrReadCacheMisses",
+ "value": 0.0026967485,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_InstrReadCacheMisses",
+ "value": 0.000084788,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryReads",
+ "value": 204845810,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataReadCacheMisses",
+ "value": 0.026058019,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataReadCacheMisses",
+ "value": 0.0001996282,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryWrites",
+ "value": 110859975,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataWriteCacheMisses",
+ "value": 0.0135986771,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataWriteCacheMisses",
+ "value": 0.0080759896,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "a8f8c4933fcae79ebfbd498723b680fb195be5c2",
+ "message": "LUAFDN-257: Backfilling tests",
+ "timestamp": "2021-11-29T23:50:37Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/258/commits/a8f8c4933fcae79ebfbd498723b680fb195be5c2"
+ },
+ "date": 1638303804007,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 104.461,
+ "range": "±12.19%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 9.7279,
+ "range": "±13.32%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalInstructionsExecuted",
+ "value": 820175672,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0028043663,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000876861,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryReads",
+ "value": 195208885,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataReadCacheMisses",
+ "value": 0.0252036735,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataReadCacheMisses",
+ "value": 0.0001953087,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryWrites",
+ "value": 108261639,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0138233544,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0075763309,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalInstructionsExecuted",
+ "value": 851903057,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_InstrReadCacheMisses",
+ "value": 0.0026929919,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_InstrReadCacheMisses",
+ "value": 0.0000848465,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryReads",
+ "value": 205205422,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataReadCacheMisses",
+ "value": 0.026036276,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataReadCacheMisses",
+ "value": 0.0001995902,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryWrites",
+ "value": 110981515,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataWriteCacheMisses",
+ "value": 0.0135920203,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataWriteCacheMisses",
+ "value": 0.0080710738,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "ee62df85c6072dd919a664ffcde25cfac4367380",
+ "message": "#nojira - Provide properly-formatted args to cachegrind",
+ "timestamp": "2021-12-13T22:01:30Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/266/commits/ee62df85c6072dd919a664ffcde25cfac4367380"
+ },
+ "date": 1642635785657,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 115.5649,
+ "range": "±12.01%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 8.7723,
+ "range": "±11.41%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalInstructionsExecuted",
+ "value": 776684157,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_InstrReadCacheMisses",
+ "value": 0.0027921234,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000928035,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryReads",
+ "value": 185646649,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataReadCacheMisses",
+ "value": 0.0258862793,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataReadCacheMisses",
+ "value": 0.0002222771,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryWrites",
+ "value": 102779867,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0140026937,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0086008576,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalInstructionsExecuted",
+ "value": 819067407,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_InstrReadCacheMisses",
+ "value": 0.0027901794,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_InstrReadCacheMisses",
+ "value": 0.0000881295,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryReads",
+ "value": 198374064,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataReadCacheMisses",
+ "value": 0.0264724727,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataReadCacheMisses",
+ "value": 0.0002096897,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryWrites",
+ "value": 107884776,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataWriteCacheMisses",
+ "value": 0.013726376,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataWriteCacheMisses",
+ "value": 0.0082497275,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "ae436d269232d09c76caa2e70a48247d7f788d00",
+ "message": "LUAFDN-707 Fix useCallback hook in DEV mode",
+ "timestamp": "2021-12-13T22:01:30Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/269/commits/ae436d269232d09c76caa2e70a48247d7f788d00"
+ },
+ "date": 1645033057736,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 116.6819,
+ "range": "±15.82%",
+ "unit": "ops/sec",
+ "extra": "50 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 8.8618,
+ "range": "±22.98%",
+ "unit": "ms/op",
+ "extra": "50 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalInstructionsExecuted",
+ "value": 781987668,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_InstrReadCacheMisses",
+ "value": 0.002748982,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_InstrReadCacheMisses",
+ "value": 0.0000925731,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryReads",
+ "value": 187148933,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataReadCacheMisses",
+ "value": 0.0258644114,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataReadCacheMisses",
+ "value": 0.0002037735,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#TotalMemoryWrites",
+ "value": 104134469,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#L1_DataWriteCacheMisses",
+ "value": 0.0139809711,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCGCold#LL_DataWriteCacheMisses",
+ "value": 0.0078426577,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalInstructionsExecuted",
+ "value": 819934978,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_InstrReadCacheMisses",
+ "value": 0.0026727083,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_InstrReadCacheMisses",
+ "value": 0.0000884351,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryReads",
+ "value": 198666581,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataReadCacheMisses",
+ "value": 0.0264090617,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataReadCacheMisses",
+ "value": 0.0001970034,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#TotalMemoryWrites",
+ "value": 108222279,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#L1_DataWriteCacheMisses",
+ "value": 0.0137140246,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "SierpinskiTriangleCG#LL_DataWriteCacheMisses",
+ "value": 0.007614994,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ }
+ ]
+ }
+ ],
+ "Concurrent FPS Benchmark": [
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "83a6aba3009e4c710e244395cb5cc65ae19351fa",
+ "message": "ci: update benchmarks workflow",
+ "timestamp": "2021-10-13T21:55:02Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/238/commits/83a6aba3009e4c710e244395cb5cc65ae19351fa"
+ },
+ "date": 1634750347531,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 60.0114,
+ "range": "±12.58%",
+ "unit": "ops/sec",
+ "extra": "602 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 19.7765,
+ "range": "±216.30%",
+ "unit": "ms/op",
+ "extra": "602 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalInstructionsExecuted",
+ "value": 1013039154,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_InstrReadCacheMisses",
+ "value": 0.0040849349,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_InstrReadCacheMisses",
+ "value": 0.0000796751,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryReads",
+ "value": 253665721,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataReadCacheMisses",
+ "value": 0.0324116596,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataReadCacheMisses",
+ "value": 0.000542734,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryWrites",
+ "value": 131835282,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataWriteCacheMisses",
+ "value": 0.0140431679,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataWriteCacheMisses",
+ "value": 0.0081291289,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalInstructionsExecuted",
+ "value": 1218786656,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_InstrReadCacheMisses",
+ "value": 0.0045957264,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_InstrReadCacheMisses",
+ "value": 0.0000616802,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryReads",
+ "value": 320031956,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataReadCacheMisses",
+ "value": 0.030312048,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataReadCacheMisses",
+ "value": 0.0004266105,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryWrites",
+ "value": 161551471,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataWriteCacheMisses",
+ "value": 0.0131213847,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataWriteCacheMisses",
+ "value": 0.0067829961,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "2b8ebac9d357b22453c27aebc5c446932d49a6c7",
+ "message": "Change thresholds to udpate FPS branch",
+ "timestamp": "2021-10-28T18:43:06Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/242/commits/2b8ebac9d357b22453c27aebc5c446932d49a6c7"
+ },
+ "date": 1635446935172,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 58.5673,
+ "range": "±15.90%",
+ "unit": "ops/sec",
+ "extra": "600 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 21.4896,
+ "range": "±235.59%",
+ "unit": "ms/op",
+ "extra": "600 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalInstructionsExecuted",
+ "value": 1008879470,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_InstrReadCacheMisses",
+ "value": 0.0040086434,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_InstrReadCacheMisses",
+ "value": 0.0000733884,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryReads",
+ "value": 251598404,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataReadCacheMisses",
+ "value": 0.0327976882,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataReadCacheMisses",
+ "value": 0.0004869347,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryWrites",
+ "value": 129712543,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataWriteCacheMisses",
+ "value": 0.0141858602,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataWriteCacheMisses",
+ "value": 0.0076575401,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalInstructionsExecuted",
+ "value": 4129467322,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_InstrReadCacheMisses",
+ "value": 0.0046357609,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_InstrReadCacheMisses",
+ "value": 0.0000194609,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryReads",
+ "value": 1256536082,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataReadCacheMisses",
+ "value": 0.0232074259,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataReadCacheMisses",
+ "value": 0.0001121146,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryWrites",
+ "value": 463163595,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataWriteCacheMisses",
+ "value": 0.0074668412,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataWriteCacheMisses",
+ "value": 0.0028789201,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "f5858b79139430febb1f073f2182654db08bd637",
+ "message": "LUAFDN-694: eliminate unnecessary allocations",
+ "timestamp": "2021-10-28T19:24:12Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/241/commits/f5858b79139430febb1f073f2182654db08bd637"
+ },
+ "date": 1635543018363,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 59.7192,
+ "range": "±17.64%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 21.0245,
+ "range": "±241.04%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalInstructionsExecuted",
+ "value": 1014207613,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_InstrReadCacheMisses",
+ "value": 0.0041423885,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_InstrReadCacheMisses",
+ "value": 0.0000748851,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryReads",
+ "value": 253011030,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataReadCacheMisses",
+ "value": 0.0320656416,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataReadCacheMisses",
+ "value": 0.0005455138,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryWrites",
+ "value": 129376670,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataWriteCacheMisses",
+ "value": 0.0142360752,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataWriteCacheMisses",
+ "value": 0.0082675725,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalInstructionsExecuted",
+ "value": 4208281411,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_InstrReadCacheMisses",
+ "value": 0.0035674021,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_InstrReadCacheMisses",
+ "value": 0.0000185235,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryReads",
+ "value": 1275457294,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataReadCacheMisses",
+ "value": 0.0222272123,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataReadCacheMisses",
+ "value": 0.000114112,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryWrites",
+ "value": 448996381,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataWriteCacheMisses",
+ "value": 0.0079286563,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataWriteCacheMisses",
+ "value": 0.002790733,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "15ddcfb84d3d69fe5f4937931a783ff1ec3df930",
+ "message": "Fix Mysterious top bar disappearing bug",
+ "timestamp": "2021-10-30T01:46:20Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/249/commits/15ddcfb84d3d69fe5f4937931a783ff1ec3df930"
+ },
+ "date": 1635830683347,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 60.517,
+ "range": "±20.48%",
+ "unit": "ops/sec",
+ "extra": "600 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 19.7953,
+ "range": "±218.99%",
+ "unit": "ms/op",
+ "extra": "600 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalInstructionsExecuted",
+ "value": 991756426,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_InstrReadCacheMisses",
+ "value": 0.0041977636,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_InstrReadCacheMisses",
+ "value": 0.0000818447,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryReads",
+ "value": 246783384,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataReadCacheMisses",
+ "value": 0.0300607799,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataReadCacheMisses",
+ "value": 0.0005671411,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryWrites",
+ "value": 126611394,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataWriteCacheMisses",
+ "value": 0.0142507474,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataWriteCacheMisses",
+ "value": 0.0080308412,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalInstructionsExecuted",
+ "value": 3965771063,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_InstrReadCacheMisses",
+ "value": 0.0035890831,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_InstrReadCacheMisses",
+ "value": 0.0000206535,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryReads",
+ "value": 1198389549,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataReadCacheMisses",
+ "value": 0.0227780875,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataReadCacheMisses",
+ "value": 0.0001329735,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryWrites",
+ "value": 425304371,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataWriteCacheMisses",
+ "value": 0.0079870752,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataWriteCacheMisses",
+ "value": 0.0027689064,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "9d261c78bbe3ce612749e4e8823c918d4acf8c31",
+ "message": "LUAFDN-692: Add assertions around the instance map to detect bad objects being inserted",
+ "timestamp": "2021-11-02T16:35:50Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/248/commits/9d261c78bbe3ce612749e4e8823c918d4acf8c31"
+ },
+ "date": 1635971043128,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 60.0177,
+ "range": "±15.02%",
+ "unit": "ops/sec",
+ "extra": "600 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 19.861,
+ "range": "±218.74%",
+ "unit": "ms/op",
+ "extra": "600 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalInstructionsExecuted",
+ "value": 1003295067,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_InstrReadCacheMisses",
+ "value": 0.0041586978,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_InstrReadCacheMisses",
+ "value": 0.0000805855,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryReads",
+ "value": 250003120,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataReadCacheMisses",
+ "value": 0.0322797811,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataReadCacheMisses",
+ "value": 0.0005553611,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryWrites",
+ "value": 127665902,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataWriteCacheMisses",
+ "value": 0.0143987155,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataWriteCacheMisses",
+ "value": 0.008427262,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalInstructionsExecuted",
+ "value": 3966298257,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_InstrReadCacheMisses",
+ "value": 0.0035920866,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_InstrReadCacheMisses",
+ "value": 0.000020449,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryReads",
+ "value": 1198368217,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataReadCacheMisses",
+ "value": 0.0231661393,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataReadCacheMisses",
+ "value": 0.0001230006,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryWrites",
+ "value": 425555803,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataWriteCacheMisses",
+ "value": 0.0080193878,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataWriteCacheMisses",
+ "value": 0.0029266409,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "f683410321e0b3eb3d4d893d1e3dd3692ef35e85",
+ "message": "LUAFDN-698: Fix possible memory leak in RobloxComponentProps.lua",
+ "timestamp": "2021-11-03T20:31:27Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/244/commits/f683410321e0b3eb3d4d893d1e3dd3692ef35e85"
+ },
+ "date": 1635977835107,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 60.1095,
+ "range": "±13.14%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 19.751,
+ "range": "±217.15%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalInstructionsExecuted",
+ "value": 1011194546,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_InstrReadCacheMisses",
+ "value": 0.0041505386,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_InstrReadCacheMisses",
+ "value": 0.0000750251,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryReads",
+ "value": 252214842,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataReadCacheMisses",
+ "value": 0.0324414374,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataReadCacheMisses",
+ "value": 0.000548013,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryWrites",
+ "value": 128512953,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataWriteCacheMisses",
+ "value": 0.014297267,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataWriteCacheMisses",
+ "value": 0.0083381634,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalInstructionsExecuted",
+ "value": 3881436891,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_InstrReadCacheMisses",
+ "value": 0.0035166435,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_InstrReadCacheMisses",
+ "value": 0.0000197192,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryReads",
+ "value": 1170916864,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataReadCacheMisses",
+ "value": 0.0227448701,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataReadCacheMisses",
+ "value": 0.0001219096,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryWrites",
+ "value": 413885656,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataWriteCacheMisses",
+ "value": 0.0080433737,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataWriteCacheMisses",
+ "value": 0.0030083816,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "eb240e688e5745b3493f682736278caf005dd4d0",
+ "message": "#nojira - Cut version rc.7",
+ "timestamp": "2021-11-04T15:57:57Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/251/commits/eb240e688e5745b3493f682736278caf005dd4d0"
+ },
+ "date": 1636041792685,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 60.3484,
+ "range": "±16.40%",
+ "unit": "ops/sec",
+ "extra": "600 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 19.8204,
+ "range": "±220.06%",
+ "unit": "ms/op",
+ "extra": "600 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalInstructionsExecuted",
+ "value": 1010476299,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_InstrReadCacheMisses",
+ "value": 0.0041487702,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_InstrReadCacheMisses",
+ "value": 0.0000755782,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryReads",
+ "value": 252027421,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataReadCacheMisses",
+ "value": 0.0325154936,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataReadCacheMisses",
+ "value": 0.0005568005,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryWrites",
+ "value": 128568726,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataWriteCacheMisses",
+ "value": 0.0142415971,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataWriteCacheMisses",
+ "value": 0.0078197633,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalInstructionsExecuted",
+ "value": 3976818770,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_InstrReadCacheMisses",
+ "value": 0.0035953522,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_InstrReadCacheMisses",
+ "value": 0.0000207098,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryReads",
+ "value": 1201783734,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataReadCacheMisses",
+ "value": 0.0230033343,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataReadCacheMisses",
+ "value": 0.0001384658,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryWrites",
+ "value": 427004977,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataWriteCacheMisses",
+ "value": 0.0079157321,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataWriteCacheMisses",
+ "value": 0.0027848364,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "353d64f2776e697b033af9735db7c32fc196255c",
+ "message": "#nojira Emergency fix to try and unblock the reverse merge",
+ "timestamp": "2021-11-04T16:32:04Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/253/commits/353d64f2776e697b033af9735db7c32fc196255c"
+ },
+ "date": 1636057639779,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 60.6545,
+ "range": "±19.20%",
+ "unit": "ops/sec",
+ "extra": "602 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 19.75,
+ "range": "±219.33%",
+ "unit": "ms/op",
+ "extra": "602 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalInstructionsExecuted",
+ "value": 1009611398,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_InstrReadCacheMisses",
+ "value": 0.0041411309,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_InstrReadCacheMisses",
+ "value": 0.0000730449,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryReads",
+ "value": 251785667,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataReadCacheMisses",
+ "value": 0.032161513,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataReadCacheMisses",
+ "value": 0.0004819655,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryWrites",
+ "value": 128386051,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataWriteCacheMisses",
+ "value": 0.0143111809,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataWriteCacheMisses",
+ "value": 0.0078203979,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalInstructionsExecuted",
+ "value": 4094817295,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_InstrReadCacheMisses",
+ "value": 0.0035834124,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_InstrReadCacheMisses",
+ "value": 0.0000196729,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryReads",
+ "value": 1239499637,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataReadCacheMisses",
+ "value": 0.0232027668,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataReadCacheMisses",
+ "value": 0.0001137943,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryWrites",
+ "value": 437795385,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataWriteCacheMisses",
+ "value": 0.0079156636,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataWriteCacheMisses",
+ "value": 0.002844829,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "d4ea3c7eb4e9d35d3428b6c0e88ef2ac2acdf2af",
+ "message": "Fix __DISABLE_ALL_WARNINGS_EXCEPT_PROP_VALIDATION__",
+ "timestamp": "2021-11-04T20:38:54Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/255/commits/d4ea3c7eb4e9d35d3428b6c0e88ef2ac2acdf2af"
+ },
+ "date": 1636580041162,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 59.8658,
+ "range": "±14.11%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 19.8721,
+ "range": "±217.88%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalInstructionsExecuted",
+ "value": 997480049,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_InstrReadCacheMisses",
+ "value": 0.0040783432,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_InstrReadCacheMisses",
+ "value": 0.0000755845,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryReads",
+ "value": 248527952,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataReadCacheMisses",
+ "value": 0.0321779339,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataReadCacheMisses",
+ "value": 0.0005451821,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryWrites",
+ "value": 127164294,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataWriteCacheMisses",
+ "value": 0.0142132901,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataWriteCacheMisses",
+ "value": 0.0079358912,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalInstructionsExecuted",
+ "value": 4046726154,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_InstrReadCacheMisses",
+ "value": 0.0048965784,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_InstrReadCacheMisses",
+ "value": 0.0000200263,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryReads",
+ "value": 1225407749,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataReadCacheMisses",
+ "value": 0.0229194152,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataReadCacheMisses",
+ "value": 0.0001199364,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryWrites",
+ "value": 433403228,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataWriteCacheMisses",
+ "value": 0.0078624541,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataWriteCacheMisses",
+ "value": 0.002774384,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "c0df797acc7f79c1e0928d1ac7ab884b156b34e5",
+ "message": "LUAFDN-723: Manually massage non-string errors into strings before re-throwing",
+ "timestamp": "2021-11-17T19:13:06Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/254/commits/c0df797acc7f79c1e0928d1ac7ab884b156b34e5"
+ },
+ "date": 1637179366448,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 59.7993,
+ "range": "±13.87%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 19.8784,
+ "range": "±216.05%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalInstructionsExecuted",
+ "value": 998974881,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_InstrReadCacheMisses",
+ "value": 0.0042061058,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_InstrReadCacheMisses",
+ "value": 0.0000746425,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryReads",
+ "value": 248544760,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataReadCacheMisses",
+ "value": 0.0321669304,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataReadCacheMisses",
+ "value": 0.0005525564,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryWrites",
+ "value": 127457892,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataWriteCacheMisses",
+ "value": 0.0142206651,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataWriteCacheMisses",
+ "value": 0.0078887387,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalInstructionsExecuted",
+ "value": 3945569644,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_InstrReadCacheMisses",
+ "value": 0.0050094161,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_InstrReadCacheMisses",
+ "value": 0.0000205659,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryReads",
+ "value": 1193244715,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataReadCacheMisses",
+ "value": 0.0237030507,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataReadCacheMisses",
+ "value": 0.0001265298,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryWrites",
+ "value": 424233663,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataWriteCacheMisses",
+ "value": 0.0079745345,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataWriteCacheMisses",
+ "value": 0.0029911016,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "0408a7e730e0d979915f8e2cc362cdd6a3e2f65d",
+ "message": "Further optimizations",
+ "timestamp": "2021-11-17T20:11:34Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/240/commits/0408a7e730e0d979915f8e2cc362cdd6a3e2f65d"
+ },
+ "date": 1637191572239,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 59.9932,
+ "range": "±15.52%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 19.8788,
+ "range": "±216.46%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalInstructionsExecuted",
+ "value": 980006820,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_InstrReadCacheMisses",
+ "value": 0.0042501255,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_InstrReadCacheMisses",
+ "value": 0.0000770719,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryReads",
+ "value": 244452045,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataReadCacheMisses",
+ "value": 0.0323344483,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataReadCacheMisses",
+ "value": 0.0005700014,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryWrites",
+ "value": 126082626,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataWriteCacheMisses",
+ "value": 0.0142460151,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataWriteCacheMisses",
+ "value": 0.0084789874,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalInstructionsExecuted",
+ "value": 3776357658,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_InstrReadCacheMisses",
+ "value": 0.005257448,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_InstrReadCacheMisses",
+ "value": 0.0000213687,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryReads",
+ "value": 1144176848,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataReadCacheMisses",
+ "value": 0.0254427723,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataReadCacheMisses",
+ "value": 0.0001306555,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryWrites",
+ "value": 405040311,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataWriteCacheMisses",
+ "value": 0.0080091781,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataWriteCacheMisses",
+ "value": 0.0029164184,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "25e7dcd87c9987a2053ec0a52efc99460a024688",
+ "message": "Separate scheduler mocking from act inlining",
+ "timestamp": "2021-11-23T18:15:18Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/259/commits/25e7dcd87c9987a2053ec0a52efc99460a024688"
+ },
+ "date": 1638223875584,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 60.0812,
+ "range": "±23.96%",
+ "unit": "ops/sec",
+ "extra": "600 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 21.1278,
+ "range": "±241.65%",
+ "unit": "ms/op",
+ "extra": "600 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalInstructionsExecuted",
+ "value": 968442913,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_InstrReadCacheMisses",
+ "value": 0.0042938845,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_InstrReadCacheMisses",
+ "value": 0.000076624,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryReads",
+ "value": 241320214,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataReadCacheMisses",
+ "value": 0.0294751811,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataReadCacheMisses",
+ "value": 0.0001892962,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryWrites",
+ "value": 125196009,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataWriteCacheMisses",
+ "value": 0.0142336726,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataWriteCacheMisses",
+ "value": 0.0083117346,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalInstructionsExecuted",
+ "value": 3997050560,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_InstrReadCacheMisses",
+ "value": 0.0057786169,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_InstrReadCacheMisses",
+ "value": 0.0000201173,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryReads",
+ "value": 1215125694,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataReadCacheMisses",
+ "value": 0.02295465,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataReadCacheMisses",
+ "value": 0.0000501685,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryWrites",
+ "value": 424866495,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataWriteCacheMisses",
+ "value": 0.0077220069,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataWriteCacheMisses",
+ "value": 0.0029660658,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "a8f8c4933fcae79ebfbd498723b680fb195be5c2",
+ "message": "LUAFDN-257: Backfilling tests",
+ "timestamp": "2021-11-29T23:50:37Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/258/commits/a8f8c4933fcae79ebfbd498723b680fb195be5c2"
+ },
+ "date": 1638303802595,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 60.2405,
+ "range": "±16.86%",
+ "unit": "ops/sec",
+ "extra": "600 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 19.8072,
+ "range": "±218.69%",
+ "unit": "ms/op",
+ "extra": "600 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalInstructionsExecuted",
+ "value": 978441008,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_InstrReadCacheMisses",
+ "value": 0.0042597233,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_InstrReadCacheMisses",
+ "value": 0.0000756591,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryReads",
+ "value": 244096114,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataReadCacheMisses",
+ "value": 0.0321219821,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataReadCacheMisses",
+ "value": 0.0001852139,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryWrites",
+ "value": 125834086,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataWriteCacheMisses",
+ "value": 0.014123987,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataWriteCacheMisses",
+ "value": 0.0083668427,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalInstructionsExecuted",
+ "value": 3585245170,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_InstrReadCacheMisses",
+ "value": 0.0057797425,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_InstrReadCacheMisses",
+ "value": 0.0000223109,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryReads",
+ "value": 1082597144,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataReadCacheMisses",
+ "value": 0.0239946042,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataReadCacheMisses",
+ "value": 0.0000530945,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryWrites",
+ "value": 387524782,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataWriteCacheMisses",
+ "value": 0.0083672946,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataWriteCacheMisses",
+ "value": 0.0029913648,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "ee62df85c6072dd919a664ffcde25cfac4367380",
+ "message": "#nojira - Provide properly-formatted args to cachegrind",
+ "timestamp": "2021-12-13T22:01:30Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/266/commits/ee62df85c6072dd919a664ffcde25cfac4367380"
+ },
+ "date": 1642635785020,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 59.8566,
+ "range": "±16.19%",
+ "unit": "ops/sec",
+ "extra": "600 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 19.9111,
+ "range": "±217.36%",
+ "unit": "ms/op",
+ "extra": "600 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalInstructionsExecuted",
+ "value": 941628238,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_InstrReadCacheMisses",
+ "value": 0.004300778,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_InstrReadCacheMisses",
+ "value": 0.0000775221,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryReads",
+ "value": 235512639,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataReadCacheMisses",
+ "value": 0.0333123905,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataReadCacheMisses",
+ "value": 0.0001961126,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryWrites",
+ "value": 121781075,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataWriteCacheMisses",
+ "value": 0.0143914315,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataWriteCacheMisses",
+ "value": 0.008622366,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalInstructionsExecuted",
+ "value": 3946457362,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_InstrReadCacheMisses",
+ "value": 0.0066009752,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_InstrReadCacheMisses",
+ "value": 0.0000186699,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryReads",
+ "value": 1190859350,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataReadCacheMisses",
+ "value": 0.023141785,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataReadCacheMisses",
+ "value": 0.0000427171,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryWrites",
+ "value": 431363069,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataWriteCacheMisses",
+ "value": 0.0074884297,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataWriteCacheMisses",
+ "value": 0.0028635622,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "ae436d269232d09c76caa2e70a48247d7f788d00",
+ "message": "LUAFDN-707 Fix useCallback hook in DEV mode",
+ "timestamp": "2021-12-13T22:01:30Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/269/commits/ae436d269232d09c76caa2e70a48247d7f788d00"
+ },
+ "date": 1645033057003,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FrameRate#FPS",
+ "value": 60.043,
+ "range": "±12.18%",
+ "unit": "ops/sec",
+ "extra": "601 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#Δt",
+ "value": 19.7027,
+ "range": "±215.42%",
+ "unit": "ms/op",
+ "extra": "601 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalInstructionsExecuted",
+ "value": 944067549,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_InstrReadCacheMisses",
+ "value": 0.0042270206,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_InstrReadCacheMisses",
+ "value": 0.0000782327,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryReads",
+ "value": 236434226,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataReadCacheMisses",
+ "value": 0.0285875489,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataReadCacheMisses",
+ "value": 0.0002143514,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#TotalMemoryWrites",
+ "value": 122583944,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#L1_DataWriteCacheMisses",
+ "value": 0.0135607237,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRateCold#LL_DataWriteCacheMisses",
+ "value": 0.0085658771,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalInstructionsExecuted",
+ "value": 3772251072,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_InstrReadCacheMisses",
+ "value": 0.0038987092,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_InstrReadCacheMisses",
+ "value": 0.000019478,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryReads",
+ "value": 1136534431,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataReadCacheMisses",
+ "value": 0.0211125826,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataReadCacheMisses",
+ "value": 0.0000422539,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#TotalMemoryWrites",
+ "value": 418628478,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#L1_DataWriteCacheMisses",
+ "value": 0.0071796907,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FrameRate#LL_DataWriteCacheMisses",
+ "value": 0.0027797798,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ }
+ ]
+ }
+ ],
+ "Concurrent First Render Benchmark": [
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "83a6aba3009e4c710e244395cb5cc65ae19351fa",
+ "message": "ci: update benchmarks workflow",
+ "timestamp": "2021-10-13T21:55:02Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/238/commits/83a6aba3009e4c710e244395cb5cc65ae19351fa"
+ },
+ "date": 1634750347681,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FirstRender#Δt",
+ "value": 75.2551,
+ "range": "±20.16%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalInstructionsExecuted",
+ "value": 1008291492,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_InstrReadCacheMisses",
+ "value": 0.0039079711,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_InstrReadCacheMisses",
+ "value": 0.0000784208,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryReads",
+ "value": 251994778,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataReadCacheMisses",
+ "value": 0.0323985285,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataReadCacheMisses",
+ "value": 0.0005412811,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryWrites",
+ "value": 131144479,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataWriteCacheMisses",
+ "value": 0.0139656889,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataWriteCacheMisses",
+ "value": 0.0076666819,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalInstructionsExecuted",
+ "value": 1301683270,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_InstrReadCacheMisses",
+ "value": 0.0047150679,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_InstrReadCacheMisses",
+ "value": 0.0000598971,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryReads",
+ "value": 339968321,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataReadCacheMisses",
+ "value": 0.0374082619,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataReadCacheMisses",
+ "value": 0.0004283487,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryWrites",
+ "value": 165581185,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataWriteCacheMisses",
+ "value": 0.0140382435,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataWriteCacheMisses",
+ "value": 0.0077772,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.500.0.5000375",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "2b8ebac9d357b22453c27aebc5c446932d49a6c7",
+ "message": "Change thresholds to udpate FPS branch",
+ "timestamp": "2021-10-28T18:43:06Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/242/commits/2b8ebac9d357b22453c27aebc5c446932d49a6c7"
+ },
+ "date": 1635446935336,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FirstRender#Δt",
+ "value": 83.4865,
+ "range": "±17.71%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalInstructionsExecuted",
+ "value": 1001097616,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_InstrReadCacheMisses",
+ "value": 0.0038882432,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_InstrReadCacheMisses",
+ "value": 0.0000737101,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryReads",
+ "value": 249069032,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataReadCacheMisses",
+ "value": 0.0327613671,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataReadCacheMisses",
+ "value": 0.0004886718,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryWrites",
+ "value": 128552459,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataWriteCacheMisses",
+ "value": 0.0143523509,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataWriteCacheMisses",
+ "value": 0.0078245022,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalInstructionsExecuted",
+ "value": 1294594763,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_InstrReadCacheMisses",
+ "value": 0.0046751325,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_InstrReadCacheMisses",
+ "value": 0.000062021,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryReads",
+ "value": 336858761,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataReadCacheMisses",
+ "value": 0.0380595118,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataReadCacheMisses",
+ "value": 0.0004172402,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryWrites",
+ "value": 162661188,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataWriteCacheMisses",
+ "value": 0.014083046,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataWriteCacheMisses",
+ "value": 0.0078572216,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.501.0.5010364",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "f5858b79139430febb1f073f2182654db08bd637",
+ "message": "LUAFDN-694: eliminate unnecessary allocations",
+ "timestamp": "2021-10-28T19:24:12Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/241/commits/f5858b79139430febb1f073f2182654db08bd637"
+ },
+ "date": 1635543018561,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FirstRender#Δt",
+ "value": 84.3454,
+ "range": "±16.45%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalInstructionsExecuted",
+ "value": 1005909468,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_InstrReadCacheMisses",
+ "value": 0.0039982604,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_InstrReadCacheMisses",
+ "value": 0.0000727451,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryReads",
+ "value": 250433445,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataReadCacheMisses",
+ "value": 0.0321654642,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataReadCacheMisses",
+ "value": 0.0005015185,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryWrites",
+ "value": 128043272,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataWriteCacheMisses",
+ "value": 0.0142848037,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataWriteCacheMisses",
+ "value": 0.0083716855,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalInstructionsExecuted",
+ "value": 1294460612,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_InstrReadCacheMisses",
+ "value": 0.0047886185,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_InstrReadCacheMisses",
+ "value": 0.0000577368,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryReads",
+ "value": 336880536,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataReadCacheMisses",
+ "value": 0.0379179491,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataReadCacheMisses",
+ "value": 0.0003835098,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryWrites",
+ "value": 160170219,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataWriteCacheMisses",
+ "value": 0.0141834544,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataWriteCacheMisses",
+ "value": 0.007577064,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "15ddcfb84d3d69fe5f4937931a783ff1ec3df930",
+ "message": "Fix Mysterious top bar disappearing bug",
+ "timestamp": "2021-10-30T01:46:20Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/249/commits/15ddcfb84d3d69fe5f4937931a783ff1ec3df930"
+ },
+ "date": 1635830683532,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FirstRender#Δt",
+ "value": 74.5102,
+ "range": "±17.46%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalInstructionsExecuted",
+ "value": 997661143,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_InstrReadCacheMisses",
+ "value": 0.0040077806,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_InstrReadCacheMisses",
+ "value": 0.000080808,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryReads",
+ "value": 248135958,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataReadCacheMisses",
+ "value": 0.0317169066,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataReadCacheMisses",
+ "value": 0.0005528985,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryWrites",
+ "value": 126755678,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataWriteCacheMisses",
+ "value": 0.0143334013,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataWriteCacheMisses",
+ "value": 0.0079465237,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalInstructionsExecuted",
+ "value": 1291094522,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_InstrReadCacheMisses",
+ "value": 0.0048074606,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_InstrReadCacheMisses",
+ "value": 0.000059606,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryReads",
+ "value": 336116352,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataReadCacheMisses",
+ "value": 0.0373720229,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataReadCacheMisses",
+ "value": 0.0004293245,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryWrites",
+ "value": 159466654,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataWriteCacheMisses",
+ "value": 0.0142697607,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataWriteCacheMisses",
+ "value": 0.0080173815,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "9d261c78bbe3ce612749e4e8823c918d4acf8c31",
+ "message": "LUAFDN-692: Add assertions around the instance map to detect bad objects being inserted",
+ "timestamp": "2021-11-02T16:35:50Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/248/commits/9d261c78bbe3ce612749e4e8823c918d4acf8c31"
+ },
+ "date": 1635971043312,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FirstRender#Δt",
+ "value": 77.2848,
+ "range": "±19.78%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalInstructionsExecuted",
+ "value": 1001633810,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_InstrReadCacheMisses",
+ "value": 0.0040008933,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_InstrReadCacheMisses",
+ "value": 0.0000796998,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryReads",
+ "value": 249342119,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataReadCacheMisses",
+ "value": 0.0323959908,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataReadCacheMisses",
+ "value": 0.0005556662,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryWrites",
+ "value": 127221524,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataWriteCacheMisses",
+ "value": 0.0142597018,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataWriteCacheMisses",
+ "value": 0.0084335022,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalInstructionsExecuted",
+ "value": 1294684784,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_InstrReadCacheMisses",
+ "value": 0.0048094108,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_InstrReadCacheMisses",
+ "value": 0.0000621371,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryReads",
+ "value": 337094356,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataReadCacheMisses",
+ "value": 0.0371542975,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataReadCacheMisses",
+ "value": 0.0004435257,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryWrites",
+ "value": 160039345,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataWriteCacheMisses",
+ "value": 0.0142141734,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataWriteCacheMisses",
+ "value": 0.0079454337,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "f683410321e0b3eb3d4d893d1e3dd3692ef35e85",
+ "message": "LUAFDN-698: Fix possible memory leak in RobloxComponentProps.lua",
+ "timestamp": "2021-11-03T20:31:27Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/244/commits/f683410321e0b3eb3d4d893d1e3dd3692ef35e85"
+ },
+ "date": 1635977835301,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FirstRender#Δt",
+ "value": 86.1342,
+ "range": "±19.40%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalInstructionsExecuted",
+ "value": 1003988674,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_InstrReadCacheMisses",
+ "value": 0.0039910968,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_InstrReadCacheMisses",
+ "value": 0.000073466,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryReads",
+ "value": 249985534,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataReadCacheMisses",
+ "value": 0.0323328149,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataReadCacheMisses",
+ "value": 0.0004852081,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryWrites",
+ "value": 127426435,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataWriteCacheMisses",
+ "value": 0.0143337683,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataWriteCacheMisses",
+ "value": 0.0079004957,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalInstructionsExecuted",
+ "value": 1298860753,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_InstrReadCacheMisses",
+ "value": 0.004793055,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_InstrReadCacheMisses",
+ "value": 0.0000617764,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryReads",
+ "value": 338239211,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataReadCacheMisses",
+ "value": 0.037879189,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataReadCacheMisses",
+ "value": 0.000408152,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryWrites",
+ "value": 160614831,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataWriteCacheMisses",
+ "value": 0.0141966466,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataWriteCacheMisses",
+ "value": 0.0075123635,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "eb240e688e5745b3493f682736278caf005dd4d0",
+ "message": "#nojira - Cut version rc.7",
+ "timestamp": "2021-11-04T15:57:57Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/251/commits/eb240e688e5745b3493f682736278caf005dd4d0"
+ },
+ "date": 1636041792868,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FirstRender#Δt",
+ "value": 68.8148,
+ "range": "±21.07%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalInstructionsExecuted",
+ "value": 1003311971,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_InstrReadCacheMisses",
+ "value": 0.0039861101,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_InstrReadCacheMisses",
+ "value": 0.0000790781,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryReads",
+ "value": 249757542,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataReadCacheMisses",
+ "value": 0.0320029935,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataReadCacheMisses",
+ "value": 0.0005453649,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryWrites",
+ "value": 127480552,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataWriteCacheMisses",
+ "value": 0.0142819432,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataWriteCacheMisses",
+ "value": 0.0078976909,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalInstructionsExecuted",
+ "value": 1297066144,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_InstrReadCacheMisses",
+ "value": 0.0047821224,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_InstrReadCacheMisses",
+ "value": 0.0000623931,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryReads",
+ "value": 337662886,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataReadCacheMisses",
+ "value": 0.0370992594,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataReadCacheMisses",
+ "value": 0.0004426131,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryWrites",
+ "value": 160257203,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataWriteCacheMisses",
+ "value": 0.014175706,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataWriteCacheMisses",
+ "value": 0.0079384201,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "353d64f2776e697b033af9735db7c32fc196255c",
+ "message": "#nojira Emergency fix to try and unblock the reverse merge",
+ "timestamp": "2021-11-04T16:32:04Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/253/commits/353d64f2776e697b033af9735db7c32fc196255c"
+ },
+ "date": 1636057639936,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FirstRender#Δt",
+ "value": 93.9404,
+ "range": "±21.53%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalInstructionsExecuted",
+ "value": 1006208057,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_InstrReadCacheMisses",
+ "value": 0.0039963375,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_InstrReadCacheMisses",
+ "value": 0.0000765349,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryReads",
+ "value": 250535713,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataReadCacheMisses",
+ "value": 0.0321945798,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataReadCacheMisses",
+ "value": 0.0005606506,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryWrites",
+ "value": 127805931,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataWriteCacheMisses",
+ "value": 0.0143071608,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataWriteCacheMisses",
+ "value": 0.0084016367,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalInstructionsExecuted",
+ "value": 1299834542,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_InstrReadCacheMisses",
+ "value": 0.0047749877,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_InstrReadCacheMisses",
+ "value": 0.000064579,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryReads",
+ "value": 338244658,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataReadCacheMisses",
+ "value": 0.0375075576,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataReadCacheMisses",
+ "value": 0.0005048919,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryWrites",
+ "value": 160503436,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataWriteCacheMisses",
+ "value": 0.0142595701,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataWriteCacheMisses",
+ "value": 0.0079954986,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.502.0.5020361",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "d4ea3c7eb4e9d35d3428b6c0e88ef2ac2acdf2af",
+ "message": "Fix __DISABLE_ALL_WARNINGS_EXCEPT_PROP_VALIDATION__",
+ "timestamp": "2021-11-04T20:38:54Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/255/commits/d4ea3c7eb4e9d35d3428b6c0e88ef2ac2acdf2af"
+ },
+ "date": 1636580041353,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FirstRender#Δt",
+ "value": 66.3608,
+ "range": "±11.73%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalInstructionsExecuted",
+ "value": 991765365,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_InstrReadCacheMisses",
+ "value": 0.0038989908,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_InstrReadCacheMisses",
+ "value": 0.0000800522,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryReads",
+ "value": 246567715,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataReadCacheMisses",
+ "value": 0.0316423097,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataReadCacheMisses",
+ "value": 0.0005537667,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryWrites",
+ "value": 126335185,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataWriteCacheMisses",
+ "value": 0.0143483306,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataWriteCacheMisses",
+ "value": 0.007993933,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalInstructionsExecuted",
+ "value": 1288064329,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_InstrReadCacheMisses",
+ "value": 0.0046744203,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_InstrReadCacheMisses",
+ "value": 0.000063142,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryReads",
+ "value": 335241794,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataReadCacheMisses",
+ "value": 0.0371333027,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataReadCacheMisses",
+ "value": 0.0004493503,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryWrites",
+ "value": 159498305,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataWriteCacheMisses",
+ "value": 0.0142605215,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataWriteCacheMisses",
+ "value": 0.0080478222,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.503.0.5030338",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "c0df797acc7f79c1e0928d1ac7ab884b156b34e5",
+ "message": "LUAFDN-723: Manually massage non-string errors into strings before re-throwing",
+ "timestamp": "2021-11-17T19:13:06Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/254/commits/c0df797acc7f79c1e0928d1ac7ab884b156b34e5"
+ },
+ "date": 1637179366643,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FirstRender#Δt",
+ "value": 77.752,
+ "range": "±14.81%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalInstructionsExecuted",
+ "value": 991403516,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_InstrReadCacheMisses",
+ "value": 0.0039822403,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_InstrReadCacheMisses",
+ "value": 0.0000804577,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryReads",
+ "value": 246149354,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataReadCacheMisses",
+ "value": 0.0325112533,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataReadCacheMisses",
+ "value": 0.000569967,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryWrites",
+ "value": 126147629,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataWriteCacheMisses",
+ "value": 0.0143439319,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataWriteCacheMisses",
+ "value": 0.0080020212,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalInstructionsExecuted",
+ "value": 1281322520,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_InstrReadCacheMisses",
+ "value": 0.004774186,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_InstrReadCacheMisses",
+ "value": 0.0000638645,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryReads",
+ "value": 332850101,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataReadCacheMisses",
+ "value": 0.037553334,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataReadCacheMisses",
+ "value": 0.0006000509,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryWrites",
+ "value": 158585723,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataWriteCacheMisses",
+ "value": 0.0141942475,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataWriteCacheMisses",
+ "value": 0.00772183,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "0408a7e730e0d979915f8e2cc362cdd6a3e2f65d",
+ "message": "Further optimizations",
+ "timestamp": "2021-11-17T20:11:34Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/240/commits/0408a7e730e0d979915f8e2cc362cdd6a3e2f65d"
+ },
+ "date": 1637191572426,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FirstRender#Δt",
+ "value": 81.263,
+ "range": "±17.51%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalInstructionsExecuted",
+ "value": 971447434,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_InstrReadCacheMisses",
+ "value": 0.0040348637,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_InstrReadCacheMisses",
+ "value": 0.0000762687,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryReads",
+ "value": 241600795,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataReadCacheMisses",
+ "value": 0.0319790711,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataReadCacheMisses",
+ "value": 0.0005194478,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryWrites",
+ "value": 124747799,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataWriteCacheMisses",
+ "value": 0.0143508103,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataWriteCacheMisses",
+ "value": 0.0085932177,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalInstructionsExecuted",
+ "value": 1242963947,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_InstrReadCacheMisses",
+ "value": 0.004930744,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_InstrReadCacheMisses",
+ "value": 0.0000645522,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryReads",
+ "value": 324137736,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataReadCacheMisses",
+ "value": 0.037276897,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataReadCacheMisses",
+ "value": 0.0004386345,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryWrites",
+ "value": 155983260,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataWriteCacheMisses",
+ "value": 0.0144418061,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataWriteCacheMisses",
+ "value": 0.0082045214,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.504.0.5040413",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "25e7dcd87c9987a2053ec0a52efc99460a024688",
+ "message": "Separate scheduler mocking from act inlining",
+ "timestamp": "2021-11-23T18:15:18Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/259/commits/25e7dcd87c9987a2053ec0a52efc99460a024688"
+ },
+ "date": 1638223875792,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FirstRender#Δt",
+ "value": 75.1508,
+ "range": "±14.50%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalInstructionsExecuted",
+ "value": 971253787,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_InstrReadCacheMisses",
+ "value": 0.0041026774,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_InstrReadCacheMisses",
+ "value": 0.0000786036,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryReads",
+ "value": 241760783,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataReadCacheMisses",
+ "value": 0.0321111716,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataReadCacheMisses",
+ "value": 0.0002487542,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryWrites",
+ "value": 124737982,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataWriteCacheMisses",
+ "value": 0.0142520985,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataWriteCacheMisses",
+ "value": 0.0084874309,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalInstructionsExecuted",
+ "value": 1241667324,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_InstrReadCacheMisses",
+ "value": 0.0049998634,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_InstrReadCacheMisses",
+ "value": 0.0000674488,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryReads",
+ "value": 324129878,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataReadCacheMisses",
+ "value": 0.0376957751,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataReadCacheMisses",
+ "value": 0.0002529542,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryWrites",
+ "value": 155906000,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataWriteCacheMisses",
+ "value": 0.0141732005,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataWriteCacheMisses",
+ "value": 0.0081301682,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "a8f8c4933fcae79ebfbd498723b680fb195be5c2",
+ "message": "LUAFDN-257: Backfilling tests",
+ "timestamp": "2021-11-29T23:50:37Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/258/commits/a8f8c4933fcae79ebfbd498723b680fb195be5c2"
+ },
+ "date": 1638303802819,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FirstRender#Δt",
+ "value": 73.9718,
+ "range": "±11.39%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalInstructionsExecuted",
+ "value": 972856014,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_InstrReadCacheMisses",
+ "value": 0.0040965877,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_InstrReadCacheMisses",
+ "value": 0.0000829773,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryReads",
+ "value": 242194304,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataReadCacheMisses",
+ "value": 0.0322182845,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataReadCacheMisses",
+ "value": 0.0002372888,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryWrites",
+ "value": 124922867,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataWriteCacheMisses",
+ "value": 0.0142667155,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataWriteCacheMisses",
+ "value": 0.008443258,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalInstructionsExecuted",
+ "value": 1246658098,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_InstrReadCacheMisses",
+ "value": 0.0049789104,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_InstrReadCacheMisses",
+ "value": 0.0000647587,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryReads",
+ "value": 325516975,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataReadCacheMisses",
+ "value": 0.0376029115,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataReadCacheMisses",
+ "value": 0.0002063917,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryWrites",
+ "value": 156528984,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataWriteCacheMisses",
+ "value": 0.0142833355,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataWriteCacheMisses",
+ "value": 0.0076302035,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.505.0.5050420",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "ee62df85c6072dd919a664ffcde25cfac4367380",
+ "message": "#nojira - Provide properly-formatted args to cachegrind",
+ "timestamp": "2021-12-13T22:01:30Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/266/commits/ee62df85c6072dd919a664ffcde25cfac4367380"
+ },
+ "date": 1642635785163,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FirstRender#Δt",
+ "value": 68.6806,
+ "range": "±14.94%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalInstructionsExecuted",
+ "value": 933921654,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_InstrReadCacheMisses",
+ "value": 0.0040463362,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_InstrReadCacheMisses",
+ "value": 0.0000779894,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryReads",
+ "value": 232889043,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataReadCacheMisses",
+ "value": 0.0330843173,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataReadCacheMisses",
+ "value": 0.0001820953,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryWrites",
+ "value": 120502797,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataWriteCacheMisses",
+ "value": 0.0143927697,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataWriteCacheMisses",
+ "value": 0.0081765156,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalInstructionsExecuted",
+ "value": 1205049163,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_InstrReadCacheMisses",
+ "value": 0.0048708278,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_InstrReadCacheMisses",
+ "value": 0.0000614606,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryReads",
+ "value": 314110851,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataReadCacheMisses",
+ "value": 0.03850193,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataReadCacheMisses",
+ "value": 0.0001673931,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryWrites",
+ "value": 152003053,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataWriteCacheMisses",
+ "value": 0.0145684837,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataWriteCacheMisses",
+ "value": 0.0082834718,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.510.0.5100270",
+ "type": "roblox"
+ }
+ ]
+ },
+ {
+ "commit": {
+ "author": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "committer": {
+ "name": "Roblox",
+ "username": "Roblox"
+ },
+ "id": "ae436d269232d09c76caa2e70a48247d7f788d00",
+ "message": "LUAFDN-707 Fix useCallback hook in DEV mode",
+ "timestamp": "2021-12-13T22:01:30Z",
+ "url": "https://github.com/Roblox/roact-alignment/pull/269/commits/ae436d269232d09c76caa2e70a48247d7f788d00"
+ },
+ "date": 1645033057185,
+ "tool": "roblox",
+ "benches": [
+ {
+ "name": "FirstRender#Δt",
+ "value": 64.0077,
+ "range": "±14.29%",
+ "unit": "ms/op",
+ "extra": "200 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalInstructionsExecuted",
+ "value": 936249185,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_InstrReadCacheMisses",
+ "value": 0.0040213856,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_InstrReadCacheMisses",
+ "value": 0.0000785144,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryReads",
+ "value": 233801688,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataReadCacheMisses",
+ "value": 0.0328478638,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataReadCacheMisses",
+ "value": 0.0002094253,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#TotalMemoryWrites",
+ "value": 121256190,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#L1_DataWriteCacheMisses",
+ "value": 0.0144906417,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRenderCold#LL_DataWriteCacheMisses",
+ "value": 0.0086891234,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalInstructionsExecuted",
+ "value": 1206104495,
+ "range": "±0%",
+ "unit": "executions",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_InstrReadCacheMisses",
+ "value": 0.0048878576,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_InstrReadCacheMisses",
+ "value": 0.0000610436,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryReads",
+ "value": 314956439,
+ "range": "±0%",
+ "unit": "reads",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataReadCacheMisses",
+ "value": 0.0380605014,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataReadCacheMisses",
+ "value": 0.0001507542,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#TotalMemoryWrites",
+ "value": 152961814,
+ "range": "±0%",
+ "unit": "writes",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#L1_DataWriteCacheMisses",
+ "value": 0.0144012479,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ },
+ {
+ "name": "FirstRender#LL_DataWriteCacheMisses",
+ "value": 0.0077704426,
+ "range": "±0%",
+ "unit": "misses/op",
+ "extra": "1 samples\nroblox-cli version: 0.514.0.5140397",
+ "type": "roblox"
+ }
+ ]
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/docs/bench/index.js b/docs/bench/index.js
new file mode 100644
index 00000000..618f0ace
--- /dev/null
+++ b/docs/bench/index.js
@@ -0,0 +1,175 @@
+"use strict";
+(function () {
+ // Colors from https://github.com/github/linguist/blob/master/lib/linguist/languages.yml
+ const toolColors = {
+ cargo: "#dea584",
+ go: "#00add8",
+ benchmarkjs: "#f1e05a",
+ pytest: "#3572a5",
+ googlecpp: "#f34b7d",
+ catch2: "#f34b7d",
+ roblox: "#000080",
+ _: "#333333",
+ };
+
+ function init(data) {
+ function collectBenchesPerTestCase(entries) {
+ const map = new Map();
+ for (const entry of entries) {
+ const { commit, date, tool, benches } = entry;
+ for (const bench of benches) {
+ const result = { commit, date, tool, bench };
+ const arr = map.get(bench.name);
+ if (arr === undefined) {
+ map.set(bench.name, [result]);
+ } else {
+ arr.push(result);
+ }
+ }
+ }
+ return map;
+ }
+
+ // Render header
+ document.getElementById("last-update").textContent = new Date(
+ data.lastUpdate
+ ).toString();
+ const repoLink = document.getElementById("repository-link");
+ repoLink.href = data.repoUrl;
+ repoLink.textContent = data.repoUrl;
+
+ // Render footer
+ document.getElementById("dl-button").onclick = () => {
+ const dataUrl = "data:," + JSON.stringify(data, null, 2);
+ const a = document.createElement("a");
+ a.href = dataUrl;
+ a.download = "benchmark_data.json";
+ a.click();
+ };
+
+ // Prepare data points for charts
+ return Object.keys(data.entries).map((name) => ({
+ name,
+ dataSet: collectBenchesPerTestCase(data.entries[name]),
+ }));
+ }
+
+ function renderAllChars(dataSets) {
+ function renderGraph(parent, name, dataset) {
+ const canvas = document.createElement("canvas");
+ canvas.className = "benchmark-chart";
+ parent.appendChild(canvas);
+
+ const color = toolColors[dataset.length > 0 ? dataset[0].tool : "_"];
+ const data = {
+ labels: dataset.map((d) => d.commit.id.slice(0, 7)),
+ datasets: [
+ {
+ label: name,
+ data: dataset.map((d) => d.bench.value),
+ borderColor: color,
+ backgroundColor: color + "60", // Add alpha for #rrggbbaa
+ },
+ ],
+ };
+ const options = {
+ scales: {
+ xAxes: [
+ {
+ scaleLabel: {
+ display: true,
+ labelString: "commit",
+ },
+ },
+ ],
+ yAxes: [
+ {
+ scaleLabel: {
+ display: true,
+ labelString: dataset.length > 0 ? dataset[0].bench.unit : "",
+ },
+ ticks: {
+ beginAtZero: true,
+ },
+ },
+ ],
+ },
+ tooltips: {
+ callbacks: {
+ afterTitle: (items) => {
+ const { index } = items[0];
+ const data = dataset[index];
+ return (
+ "\n" +
+ data.commit.message +
+ "\n\n" +
+ data.commit.timestamp +
+ " committed by @" +
+ data.commit.committer.username +
+ "\n"
+ );
+ },
+ label: (item) => {
+ let label = item.value;
+ const { range, unit } = dataset[item.index].bench;
+ label += " " + unit;
+ if (range) {
+ label += " (" + range + ")";
+ }
+ return label;
+ },
+ afterLabel: (item) => {
+ const { extra } = dataset[item.index].bench;
+ return extra ? "\n" + extra : "";
+ },
+ },
+ },
+ onClick: (_mouseEvent, activeElems) => {
+ if (activeElems.length === 0) {
+ return;
+ }
+ // XXX: Undocumented. How can we know the index?
+ const index = activeElems[0]._index;
+ const url = dataset[index].commit.url;
+ window.open(url, "_blank");
+ },
+ };
+
+ new Chart(canvas, {
+ type: "line",
+ data,
+ options,
+ });
+ }
+
+ function renderBenchSet(name, benchSet, main) {
+ const setElem = document.createElement("div");
+ setElem.className = "benchmark-set";
+ main.appendChild(setElem);
+
+ const nameElem = document.createElement("h1");
+ nameElem.className = "benchmark-title";
+ nameElem.textContent = name;
+ setElem.appendChild(nameElem);
+
+ const graphsElem = document.createElement("div");
+ graphsElem.className = "benchmark-graphs";
+ setElem.appendChild(graphsElem);
+
+ for (const [benchName, benches] of benchSet.entries()) {
+ renderGraph(graphsElem, benchName, benches);
+ }
+ }
+
+ const main = document.getElementById("main");
+ for (const { name, dataSet } of dataSets) {
+ renderBenchSet(name, dataSet, main);
+ }
+ }
+
+ fetch("./data.json")
+ .then((response) => response.json())
+ .then((data) => {
+ renderAllChars(init(data));
+ });
+})();
diff --git a/docs/configuration.md b/docs/configuration.md
new file mode 100644
index 00000000..1275c566
--- /dev/null
+++ b/docs/configuration.md
@@ -0,0 +1,70 @@
+
+Roact can currently be configured by assigning values to a small number of special global variables. Typically, they need to be set up before `React` or `ReactRoblox` is initialized via `require`. There are a couple of ways to do this:
+
+* Set globals using the `lua.globals` argument to tooling like `robloxdev-cli`
+ * If you're using `lest`, you can do this [in a configuration file](https://github.com/Roblox/lest/#configuration)
+* Assign values immediately at the beginning of the entry point script in your project
+ * For lua-apps projects, you can do this conditionally by checking if you're in studio in your project's `StarterScript`, and then assigning globals accordingly
+
+## Globals
+
+### \_\_DEV\_\_
+Enabling `_G.__DEV__` enables "Dev Mode", a general-purpose option that sacrifices performance to enable a number of features that improve the development experience:
+
+* Component `render` methods are run twice to ensure that no side-effects are being counted upon
+* Warnings for behavior that violates Roact rules and best practices, like:
+ * Reading state when it hasn't been initialized
+ * Calling `setState` before a component has mounted
+ * Assigning [multiple keys](deviations.md#stable-keys) to a component
+ * Failing to assign keys to elements in a list (a potential de-optimization)
+* Warnings for the use of deprecated components or features
+* Validation of properties passed into components via [`validateProps` or `propTypes`](deviations.md#property-validation)
+
+You should enable Dev Mode in any or all of the following situations:
+
+* Running unit tests and rhodium tests
+* Running storybooks
+* Developing and testing locally as you work
+
+**Dev Mode is _not_ meant to be enabled on production.** While it exposes a great deal of useful information and introduces extra assurances, it pays a hefty performance cost to do so.
+
+!!! info
+ In the future, projects will use tools like [darklua](https://gitlab.com/seaofvoices/darklua) to automatically remove all code branches that check for Dev Mode when creating bundles for production. This reduces the overhead of branching on Dev Mode logic and saves a little bit of extra performance in places where it matters.
+
+### \_\_DISABLE_ALL_WARNINGS_EXCEPT_PROP_VALIDATION\_\_
+Occasionally, some older projects will issue more warnings in Dev Mode than can easily be resolved. In order to introduce prop validation but silence all other Dev Mode warnings, set the `__DISABLE_ALL_WARNINGS_EXCEPT_PROP_VALIDATION__` global to `true`.
+
+!!! info
+ Typically, this is only necessary in tests that are strict about reducing warning output. In general, prefer the full-featured Dev Mode.
+
+### \_\_COMPAT_WARNINGS\_\_
+Enables compatibility warnings for any uses of outdated APIs in your code. These compatibility mismatches should have no effect on behavior, but can be modernized to better align to standards and anticipate future releases. Compat warnings will help you surface uses of outdated APIs when you [migrate from Roact 1.x](migrating-from-roact-1x.md/#updating-conventions-and-apis).
+
+
+### \_\_ROACT_17_MOCK_SCHEDULER\_\_
+Ensure that Roact's internal scheduler is mocked instead of using real async logic like `task.delay`. This is useful in conjunction with [the `act` function](api-reference/react-roblox.md#reactrobloxact) to test concurrent behavior via the "arrange-act-assert" pattern.
+
+Use this global in test configuration to make sure that you're not inadvertently relying on asynchronous logic in tests. Since Roact 17 uses concurrent rendering by default, you will always need this global to be set to `true` (except when using the [`__ROACT_17_COMPAT_LEGACY_ROOT__`](#ROACT17COMPATLEGACYROOT) global described below).
+
+!!! caution
+ In future updates, Roact should _always_ mock the scheduler when in a testing environment and avoid extra configuration. For now, Roact favors explicitness while we shore up the testing experience.
+
+### \_\_ROACT_17_INLINE_ACT\_\_
+This global will automatically wrap the behavior of `RoactCompat.mount`, `RoactCompat.update`, and `RoactCompat.unmount` in `ReactRoblox.act`, which ensures that queued actions will be played forward by the mocked scheduler.
+
+Use this global to shore up existing tests that may not be
+
+**This is intended for tests only, and will not work correctly unless `__ROACT_17_MOCK_SCHEDULER__` is also enabled.**
+
+### \_\_ROACT_17_COMPAT_LEGACY_ROOT\_\_
+Ensures that the `RoactCompat.mount` compatibility function creates a Legacy Root instead of a Concurrent Root, which is the default behavior.
+
+Use this global to preserve old behavior in certain testing scenarios. If you need to explicitly rely on a legacy root in production, consider opting for the [`createLegacyRoot`](api-reference/react-roblox.md#reactrobloxcreatelegacyroot) API instead.
+
+
\ No newline at end of file
diff --git a/docs/deviations.md b/docs/deviations.md
index be3766f2..ae0bf03a 100644
--- a/docs/deviations.md
+++ b/docs/deviations.md
@@ -1,16 +1,203 @@
-# Deviations
-The Roact alignment effort aims to map as closely to React's API as possible, but there are a few places where language deviations require us to omit functionality or deviate our approach.
+While Roact has been architected to align with React JS's APIs and idioms, a small number of deviations have been introduced for one or several of the following reasons:
+
+* Differences between JavaScript and Luau
+* Differences between Roblox and the HTML DOM
+* Supporting features from legacy Roact that are not in React JS
+* Easier adoption of Roact 17+ by users of legacy Roact
+
+The following list attempts to comprehensively describe all of the differences between Roact 17+ and its equivalent upstream version in React JS. It is intended to be a companion to the [Roact 17 adoption guide](migrating-from-1x/adopt-new-features.md), which focuses more on the differences between legacy Roact and Roact 17+.
+
+## JSX
+
+The Luau ecosystem does not yet have the tooling to support JSX. Instead, use `React.createElement` as your primary tool for building UIs with Roact 17. Element construction in Roact is exactly like [using React without JSX](https://react.dev/reference/react/createElement#creating-an-element-without-jsx).
+
+!!! info
+ Future support for a JSX-equivalent feature for Luau has been proposed, and will be considered as Roact 17+ is adopted.
+
+## React.useState
+
+`React.useState` returns two values rather than an array containing two values.
+
+Luau does not have syntactic sugar for destructuring like javascript:
+```js
+const [value, setValue] = React.useState(0);
+```
+
+However, it _does_ support multiple return values, so we can support a very similar usage:
+```lua
+local value, setValue = React.useState(0)
+```
+
+## Hook Dependency Arrays
+
+### Dependency Arrays
+
+In React JS, some hooks [accept an array of dependency values](https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects) that determine when they need to be re-invoked:
+```js
+useEffect(() => {
+ document.title = `You clicked ${count} times`;
+}, [count]); // Only re-run the effect if count changes
+```
+
+The dependency array should always be specified inline rather than composed dynamically to ensure that it accurately reflects all values relevant to the hook, whatever their state may be during a given render.
+
+React JS provides warnings in DEV mode when a dependency array changes length between renders, an indication that the feature isn't being used correctly. It additionally provides a linter plugin that can help enforce these rules and prevent mistakes during development: https://www.npmjs.com/package/eslint-plugin-react-hooks
+
+### Deviations for Luau
+
+Hooks in Roact 17+ aim to treat dependency arrays exactly like React JS. However, arrays in luau that contain nil-able values might be indistinguishable from similar arrays that simply have different lengths.
+
+For example:
+```lua
+print(#{"A", "B", nil} == #{ "A", "B" }) -- prints: true
+```
+
+This means that, if we align behavior directly with React JS, we introduce a possible scenario in which a correctly-specified dependency array triggers warnings about differences in length:
+```lua
+local root = ReactRoblox.createRoot(someContainer)
+local function Component(props: { A: number, B: number? })
+ React.useEffect(function()
+ -- Trigger some effect
+ end, { props.A, props.B })
+
+ return nil
+end
+
+-- does not warn:
+root:render(React.createElement(Component, { A = 1, B = 2 }))
+-- subsequent render warns about different length arrays:
+root:render(React.createElement(Component, { A = 1 }))
+```
+
+To address this and support the same API as React JS, Roact 17 introduces two minor deviations:
+
+* If a dependency array changes in length, **a re-render will always be triggered** (in React JS, the comparison is short-circuited in production with the assumption that warnings had been ignored or addressed)
+* If a dependency array changes in length, we assume the developer provided an array ending with one or more nil-able values, and we **suppress the warning**
+
+Hooks affected:
+
+* `useEffect`
+* `useLayoutEffect`
+* `useMemo`
+* `useCallback`
+
+## Stable Keys
+
+In React JS, the reserved "key" prop is used to provide stable identities to DOM elements. This improves performance when list-like data is reordered by helping React understand which elements are which, instead of simply modifying the element at each position to line up with the new ordering (more info in the [React documentation](https://reactjs.org/docs/lists-and-keys.html)).
+
+Since order has no inherent meaning in Roblox's DOM, legacy Roact generally expected children to be provided as a _map_ instead of an array, where the keys to the map are the stable keys associated with the elements. This behavior was used instead of a reserved "key" prop (more info in the [Roact documentation](https://roblox.github.io/roact/performance/reduce-reconciliation/#stable-keys)).
+
+Roact 17+ supports _both_ methods for providing keys. Both of the following examples are valid and equivalent.
+
+With table keys:
+```lua
+-- Returns a fragment of items in an ordered list
+function NumberList(props)
+ local numbers = props.numbers
+ for i, number in numbers do
+ local key = tostring(i)
+ listItems[key] = React.createElement("TextLabel", {
+ Text = key,
+ LayoutOrder = i,
+ })
+ end);
+ return listItems
+end
+```
+
+Using the special "key" prop:
+```lua
+-- Returns a fragment of items in an ordered list
+function NumberList(props)
+ local numbers = props.numbers
+ for i, number in numbers do
+ local key = tostring(i)
+ local element = React.createElement("TextLabel", {
+ key = key,
+ Text = key,
+ LayoutOrder = i,
+ })
+ table.insert(listItems, element)
+ end);
+ return listItems
+end
+```
+
+If your component provides keys using both methods at the same time, Roact will consider this a mistake and print a warning. The following code would result in a warning:
+```lua
+return React.createElement("Frame", nil, {
+ Label = React.createElement("TextLabel", {
+ key = "label1",
+ Text = "Hello",
+ })
+})
+```
+
+In the above example, Roact doesn't know whether you wanted to use "label1" or "Label" as the key, so it falls back to the explicitly provided key ("label1"). In [Dev Mode](configuration.md#dev), it will output an appropriate warning as well.
## Class Components
-Lua does not have ES6's `class` semantics. For class components, Roact will expose `Component:extend(name: string) -> Component` to bridge this gap. Equivalently, `PureComponent:extend` is used to define PureComponents.
+Luau does not currently have ES6's `class` semantics. For class components, Roact exposes an `extend` method to provide equivalent behavior.
+
+### React.Component:extend
+```
+React.Component:extend(name: string): ReactComponent
+```
+The `extend` method on components replaces the `extend` behavior used in ES6's class components. It returns a React component definition, which can then be used to define lifecycle methods.
+
+For example, a class component in Roact can be created like this:
+```lua
+local MyComponent = React.Component:extend("MyComponent")
+
+function MyComponent:render()
+ return React.createElement("TextLabel", {Text = self.props.text})
+end
+
+function MyComponent:componentDidMount()
+ print("rendered with text " .. self.props.text)
+end
+```
+
+Equivalently, `React.PureComponent:extend` is used to define PureComponents.
### Constructors
-Because of the lack of class semantics, there are also no inheritable constructors; instead, Roact provides a lifecycle method called `init` that takes the place of the constructor, running immediately after an instance is created for that class.
+Since Luau currently lacks a `class` feature, there are also no inheritable constructors; instead, Roact provides a lifecycle method called `init` that takes the place of the constructor, running immediately after an instance of that class is created.
+
+For all intents and purposes, this should behave exactly like a constructor for a class component in React JS, except that there is no `super` logic needed.
+
+### Calling `setState` in Constructors
+In React JS, `setState` is not allowed inside component constructors. Instead, React documentation suggests that `this.state` should be assigned to directly, but _never anywhere else_.
+
+Legacy Roact opts to allow `setState` inside of the `init` method (equivalent to a constructor), because it allows documentation to consistently warn against assigning directly to `self.state`. However, for backwards compatibility, it still supports direct assignments to `self.state` in `init`.
+
+#### Recommended Use
+
+As with legacy Roact, Roact 17 allows both direct assignment and use of `setState`. This allows guidance from legacy Roact documentation and common practice to remain accurate.
+
+In Roact 17+, it is still recommended to use `setState` inside of component `init` methods. This means that you will _always_ avoid assigning directly to `self.state`.
+
+#### Behavior
+
+When used in a constructor, `setState` will treat the `updater` argument exactly as it does elsewhere.
+
+* If `setState` is called multiple times in a constructor, each subsequent update will be merged into previous state
+* The `updater` argument can be a function that accepts previous state and returns a new table that will be merged into any previous state
+* The `updater` argument can be a table that will be merged into any previous state
-For all intents and purposes, this should function exactly like a constructor for a class component in React, except that there is no need to call `super`.
+!!! caution
+ When using `setState` in a constructor, the optional `callback` argument will not be used. Instead, consider putting the desired behavior in a `componentDidMount` implementation.
+
+### Error Boundaries
+
+Error boundaries are not yet fully supported due to a limitation in Luau around recursive `pcall` depth. Future updates to React will unravel the recursive traversal and enable these features.
+
+### Property Validation
+The legacy api `validateProps` is still present and has a backwards-compatible API.
## Function Components
-JavaScript Functions are also objects, which means that they can have member fields defined on them. Lua/Luau does not allow this.
+In JavaScript, functions are also objects, which means that they can have member fields defined on them. Luau does not allow this, so some features are not available on function components.
+
+!!! info
+ With the introduction of Hooks, function components are the preferred style of component definition. Giving up features like `defaultProps` and prop validation is not ideal, so future API additions may provide a way to create smarter function components.
### defaultProps
For the time being, function components do not support the `defaultProps` feature. In the future, we may want to re-implement it in terms of hooks to make sure that function components with hooks are as appealing and feature-rich as possible.
@@ -18,5 +205,85 @@ For the time being, function components do not support the `defaultProps` featur
### propTypes
For the time being, function components do not support the `propTypes` feature. While propTypes is less often used and can in many cases be superseded by static type checking, we may want to, in the future, re-implement it in terms of hooks to make sure that function components with hooks are as appealing and feature-rich as possible.
-### contextTypes
-For the time being, function components do not support the `contextTypes` feature. While contextTypes is less often used and can in many cases be superseded by static type checking, we may want to, in the future, re-implement it in terms of hooks to make sure that function components with hooks are as appealing and feature-rich as possible.
+### validateProps
+In Roact 17, we continue to support legacy Roact's `validateProps`. Prior Roact documentation on this method can be found [here](https://roblox.github.io/roact/api-reference/#validateprops).
+
+## Bindings and Refs
+Roact supports callback refs, refs created using `React.createRef`, and refs using the `React.useRef` hook. However, under the hood, Refs are built on top of a concept called Bindings.
+
+### Bindings
+Roact introduces a bindings feature that provides a unidirectional data binding that can be updated outside of the render cycle (much like refs could).
+
+For now, bindings are documented in more detail [here](https://roblox.github.io/roact/advanced/bindings-and-refs/#bindings).
+
+### Host Properties with Instance Values
+The Roblox API exposes certain host properties that must be assigned _Instance references_ as values. Effectively, there are native APIs that expect a `ref.current` value as a value.
+
+The logic of bindings is a perfect fit for this scenario. Consider the following example:
+```lua
+local PopupButtons = Roact.Component:extend("PopupButtons")
+function PopupButtons:init()
+ self.confirmRef = Roact.createRef()
+ self.cancelRef = Roact.createRef()
+end
+function PopupButtons:render()
+ --[[
+ "Some Description"
+ [ Confirm ] [ Cancel ]
+ ]]
+ return Roact.createElement("Frame", nil {
+ ConfirmButton = Roact.createElement("TextButton", {
+ [Roact.Ref] = self.confirmRef,
+ Text = "Confirm",
+ NextSelectionRight = self.cancelRef.value,
+ }),
+ CancelButton = Roact.createElement("TextButton", {
+ [Roact.Ref] = self.cancelRef,
+ Text = "Confirm",
+ NextSelectionLeft = self.confirmRef.value,
+ }),
+ })
+end
+```
+This example poses a problem. Since children will be rendered in an arbitrary order, one of the following will happen:
+
+1. Confirm Button renders first and its ref is assigned
+2. Confirm Button's NextSelectionRight property is set to the Cancel Button's ref, **which is currently nil**
+3. Cancel Button renders and its ref is assigned
+4. Cancel Button's NextSelectionLeft property is properly set to the Confirm Button's ref
+
+Or:
+
+1. Cancel Button renders first and its ref is assigned
+2. Cancel Button's NextSelectionLeft property is set to the Confirm Button's ref, **which is currently nil**
+3. Confirm Button renders and its ref is assigned
+4. Confirm Button's NextSelectionRight property is properly set to the Cancel Button's ref
+
+Thus, it would require much more trickery to make even a simple gamepad neighbor assignment work correctly. However *with refs implemented as bindings*, the above scenario can be solved pretty simply:
+```lua
+-- ...
+ return Roact.createElement("Frame", nil {
+ ConfirmButton = Roact.createElement("TextButton", {
+ [Roact.Ref] = self.confirmRef,
+ Text = "Confirm",
+ -- pass the ref itself, which is a binding
+ NextSelectionRight = self.cancelRef,
+ }),
+ CancelButton = Roact.createElement("TextButton", {
+ [Roact.Ref] = self.cancelRef,
+ Text = "Confirm",
+ -- pass the ref itself, which is a binding
+ NextSelectionLeft = self.confirmRef,
+ }),
+ })
+-- ...
+```
+With the above implementation, something like the following happens:
+
+1. Confirm Button renders first and its ref is assigned
+2. Confirm Button's NextSelectionRight property is set to the Cancel Button's ref, **which is currently nil**
+3. Cancel Button renders and its ref is assigned
+ * The binding value updates, and the Confirm button's NextSelectionRight property is assigned to the Cancel Button's new ref value
+4. Cancel Button's NextSelectionLeft property is properly set to the Confirm Button's ref
+
+...or the inverse, with the Cancel Button rendering first. Either way, both refs are assigned, and both neighbor properties are assigned by the time the render is complete.
diff --git a/docs/diff-bridge-shutdown-method-after.png b/docs/diff-bridge-shutdown-method-after.png
new file mode 100644
index 00000000..16683ab0
Binary files /dev/null and b/docs/diff-bridge-shutdown-method-after.png differ
diff --git a/docs/diff-bridge-shutdown-method-before.png b/docs/diff-bridge-shutdown-method-before.png
new file mode 100644
index 00000000..fb24d1b1
Binary files /dev/null and b/docs/diff-bridge-shutdown-method-before.png differ
diff --git a/docs/diff-class-arrow-function-method-new.png b/docs/diff-class-arrow-function-method-new.png
new file mode 100644
index 00000000..cad47af1
Binary files /dev/null and b/docs/diff-class-arrow-function-method-new.png differ
diff --git a/docs/diff-class-arrow-function-method-old.png b/docs/diff-class-arrow-function-method-old.png
new file mode 100644
index 00000000..2a5b9f4c
Binary files /dev/null and b/docs/diff-class-arrow-function-method-old.png differ
diff --git a/docs/diff-packages.png b/docs/diff-packages.png
new file mode 100644
index 00000000..4a71848b
Binary files /dev/null and b/docs/diff-packages.png differ
diff --git a/docs/diff-spread-flow-type-no-deviation-anymore.png b/docs/diff-spread-flow-type-no-deviation-anymore.png
new file mode 100644
index 00000000..9bb0caa7
Binary files /dev/null and b/docs/diff-spread-flow-type-no-deviation-anymore.png differ
diff --git a/docs/diff-upstream-comment.png b/docs/diff-upstream-comment.png
new file mode 100644
index 00000000..4ae6f6a4
Binary files /dev/null and b/docs/diff-upstream-comment.png differ
diff --git a/docs/extra.css b/docs/extra.css
new file mode 100644
index 00000000..79bf679a
--- /dev/null
+++ b/docs/extra.css
@@ -0,0 +1,3 @@
+.md-typeset hr {
+ border-bottom: 2px solid rgba(0, 0, 0, 0.15);
+}
diff --git a/docs/images/aligned.svg b/docs/images/aligned.svg
new file mode 100644
index 00000000..3111f889
--- /dev/null
+++ b/docs/images/aligned.svg
@@ -0,0 +1 @@
+Aligned Aligned
\ No newline at end of file
diff --git a/docs/images/apichange.svg b/docs/images/apichange.svg
new file mode 100644
index 00000000..4c223362
--- /dev/null
+++ b/docs/images/apichange.svg
@@ -0,0 +1 @@
+API Change API Change
\ No newline at end of file
diff --git a/docs/images/deviation.svg b/docs/images/deviation.svg
new file mode 100644
index 00000000..4a93fd91
--- /dev/null
+++ b/docs/images/deviation.svg
@@ -0,0 +1,9 @@
+
+ Deviation
+
+
+
+
+ Deviation
+
+
\ No newline at end of file
diff --git a/docs/images/reactjs.svg b/docs/images/reactjs.svg
new file mode 100644
index 00000000..b17e85ec
--- /dev/null
+++ b/docs/images/reactjs.svg
@@ -0,0 +1,12 @@
+
+ View React Docs
+
+
+
+
+
+
+
+ View React Docs
+
+
diff --git a/docs/images/roblox.svg b/docs/images/roblox.svg
new file mode 100644
index 00000000..95871d85
--- /dev/null
+++ b/docs/images/roblox.svg
@@ -0,0 +1 @@
+Roblox Roblox
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 00000000..497580b6
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,21 @@
+Roact is a lua port of Facebook's [React](https://reactjs.org) UI library.
+
+By and large, [React's documentation](https://reactjs.org/docs/getting-started.html) should be able to serve most Roact users' needs. This documentation site serves as a comprehensive guide to the _differences_ between Roact and React.
+
+If you're new to the React library and want **to start learning the concepts of React**, begin with the [React JS documentation](https://reactjs.org/docs/getting-started.html).
+
+If you want **to find out if a React feature is present in Roact (and if there are any differences to be aware of)**, check out the [API Reference](api-reference/react.md).
+
+If you're familiar with React and want **to learn where Roact differs**, start with the [Deviations page](deviations.md).
+
+And if you want **to migrate an existing project from Roact 1.x to Roact 17+**, check out the guide on [Migrating From Roact 1.x](migrating-from-1x/minimum-requirements.md).
+
+### Which Part is "Roact"?
+
+Previously, **Roact** was the name that referred to a single package. It implemented a similar API to that of React 15. This documentation refers to those older versions of Roact as **legacy Roact** or **Roact 1.x**.
+
+Today, **Roact** is used as an umbrella term to describe the collection of packages that compose the Luau port of the React library. This collection of packages includes a few top-level ones, namely `React`, `ReactRoblox`, and `RoactCompat`. This documentation refers to the initial release of this version of Roact as **Roact 17** (because it aligns its implementation to React JS version 17.0.1) or **Roact 17+** (to include future releases).
+
+The originating React UI library, written in JavaScript, is referred to in this documentation as **React JS** in order to clearly disambiguate it from Roact and from the top-level package called `React`. In can be considered an umbrella term for the collection of packages, similar to "Roact".
+
+This documentation endeavors to use these terms consistently throughout to avoid confusion.
diff --git a/docs/migrating-from-1x/adopt-new-features.md b/docs/migrating-from-1x/adopt-new-features.md
new file mode 100644
index 00000000..90c2faf1
--- /dev/null
+++ b/docs/migrating-from-1x/adopt-new-features.md
@@ -0,0 +1,241 @@
+# Adopt New Features
+
+Roact 17 ships with a number of features new to Roact that have been ported from React JS, in addition to a couple of new capabilities unique to the Roact ecosystem.
+
+## Asynchronous Rendering
+
+Roact 17 introduces a paradigm shift to the underlying rendering behavior that allows it to divide work across multiple frames and preserve high framerate and interactivity.
+
+This behavior is called [Concurrent Mode](https://17.reactjs.org/docs/concurrent-mode-intro.html#what-is-concurrent-mode) rendering.
+
+**Roact 17 will use Concurrent Mode by default in its `mount` compatibility layer.**
+
+!!! info
+ Roact 17 is aligned to React JS 17.0.1, which means that it has not inherited any [changes to Concurrent Mode described in the React 18 documentation](https://reactjs.org/blog/2022/03/29/react-v18.html#gradually-adopting-concurrent-features). As we begin to build _Roact_ 18, we may shift our distinctions similarly. For now, **opting into Concurrent Mode is the best way to get the latest optimizations and ensure that your components are robust.**
+
+### ReactRoblox.createRoot
+
+In legacy Roact, the [`Roact.mount`](https://roblox.github.io/roact/api-reference/#roactmount) function is used to render a component tree. In React JS 17 and older, the primary entry-point for rendering a component tree is [`ReactDOM.render`](https://reactjs.org/docs/react-dom.html#render), while the experimental `ReactDOM.createRoot` API is used to adopt Concurrent Mode.
+
+Roact 17 skips introducing the top-level `render` function from earlier versions of React JS, and instead provides the following for mounting Roact UI elements:
+
+* The `createRoot`, `createBlockingRoot`, and `createLegacyRoot` APIs from React JS 17
+* A [compatibility layer](../api-reference/roact-compat.md#roactcompatmount) that exports a `mount` function aligned with legacy Roact's API
+
+In new code, you should always use [`ReactRoblox.createRoot`](https://reactjs.org/docs/concurrent-mode-reference.html#createroot). The vast majority of existing Roact code should be compatible with `createRoot`, which enables Concurrent Mode and can greatly improve responsiveness for complex applications.
+
+If you run into problems with Concurrent Mode that are difficult to address, you may be interested in considering [`ReactRoblox.createBlockingRoot` or `ReactRoblox.createLegacyRoot`](https://reactjs.org/docs/concurrent-mode-reference.html#createblockingroot) at a cost to overall app responsiveness. Reach out to #lua-foundation before proceeding with any new code using these APIs.
+
+### ReactRoblox.act
+
+When Concurrent Mode is enabled, Roact will attempt to schedule work evenly across rendering frames to keep the application running smoothly, even when UI rendering work has been queued up.
+
+However, this means that tests relying on synchronous rendering behavior will no longer function correctly. To fix this, use the [`ReactRoblox.act`](../api-reference/react-roblox.md#reactrobloxact) function to play scheduler logic forward (also re-exported as [`RoactCompat.act`](../api-reference/roact-compat.md#roactcompatact)).
+
+#### How to Use `act`
+
+The `ReactRoblox.act` utility works by:
+
+1. Running the provided function
+2. Performing queued work by playing forward Roact's internal scheduler
+3. Repeating step 2 until the queue is empty
+
+When running tests using the mock scheduler, the following scenarios will need to be wrapped in an `act` call:
+
+* Rendering an initial tree with the `render` method of a React root or the `RoactCompat.mount` compatibility function (if the [`__ROACT_17_INLINE_ACT__`](../configuration.md#__roact_17_inline_act__) global is set to true, this will happen **automatically** for `mount`, `update`, and `unmount`)
+* Rendering updates with the `render` method of a React root
+* Unmounting a tree by passing `nil` to the `render` method of a React root
+* Calling `task.wait` or other yielding functions to allow engine callbacks to fire
+* Triggering behavior that causes a component to update its state, including firing signals that your component has subscribed to
+* Processing user inputs generated by Rhodium
+ * Keep in mind that most input events only fire if the target elements meet certain requirements (on screen, visible, enabled).
+ * Virtual input events may need to be explicitly flushed before events fire and relevant Roact work is queued (see example below)
+
+!!! info
+ In order to enable the `act` function, you'll need Roact to use the mocked version of its internal scheduler. To do this, set the [`__ROACT_17_MOCK_SCHEDULER__`](../configuration.md#__roact_17_mock_scheduler__) global to true in your testing configuration.
+
+#### Example
+
+Many integration tests involve validating various states of a component and interacting with the component via virtual input events or mocked signals.
+The following is a comprehensive example of several of the above scenarios in practice.
+
+Suppose we have a component called `TooltipButton`:
+```lua
+local function TooltipButton(props)
+ local showTooltip, setShowTooltip = React.useState(false)
+
+ return React.createElement("Frame", {
+ key = "TooltipButton",
+ Size = UDim2.fromScale(1, 1),
+ }, {
+ Button = React.createElement("TextButton", {
+ Text = "Show tooltip",
+ Active = props.enabled,
+ [React.Event.Activated] = function()
+ setShowTooltip(true)
+ task.delay(props.tooltipFadeDelay, function()
+ setShowTooltip(false)
+ end)
+ end,
+ })
+ Tooltip = if showTooltip
+ then React.createElement("TextLabel", {
+ Text = "Tooltip text!",
+ })
+ else nil
+ })
+end
+```
+
+We'd like to write a test to validate the tooltip behavior. Here's how we might use `act` to guarantee that all scheduled work is flushed and our test works as expected:
+```lua
+local React = require(Packages.React)
+local ReactRoblox = require(Packages.ReactRoblox)
+
+local container, root
+beforeEach(function()
+ container = Instance.new("ScreenGui")
+ container.Parent = Players.LocalPlayer.PlayerGui
+
+ root = ReactRoblox.createRoot(container)
+end)
+
+afterEach(function()
+ container:Destroy()
+end)
+
+it("shows a tooltip on click and hides it after a delay", function()
+ -- Use `act` for the initial render
+ ReactRoblox.act(function()
+ -- Render the button in a disabled state
+ root:render(React.createElement(TooltipButton, {
+ enabled = false,
+ tooltipFadeDelay = 1,
+ }))
+ end)
+
+ expect(container.TooltipButton.Tooltip).toBeNil()
+ expect(container.TooltipButton.Button.Active).toBe(false)
+
+ -- Use `act` to re-render the tree
+ ReactRoblox.act(function()
+ -- Rerender in the enabled state
+ root:render(React.createElement(TooltipButton, {
+ enabled = true,
+ tooltipFadeDelay = 1,
+ }))
+ end)
+
+ expect(container.TooltipButton.Tooltip).toBeNil()
+ expect(container.TooltipButton.Button.Active).toBe(true)
+
+ -- Use `act` to trigger virtual input
+ local element = Rhodium.Element.new(container.TooltipButton.Button)
+ ReactRoblox.act(function()
+ -- Click the button to trigger the tooltip
+ element:click()
+ Rhodium.VirtualInput.waitForInputEventsProcessed()
+ end)
+
+ expect(container.TooltipButton.Tooltip).never.toBeNil()
+ expect(container.TooltipButton.Tooltip.Text).toEqual("Tooltip text!")
+
+ -- Use `act` to resume queued renders after a delayed callback fires
+ ReactRoblox.act(function()
+ task.wait(1)
+ end)
+
+ expect(container.TooltipButton.Tooltip).toBeNil()
+end)
+```
+
+For more details and examples, refer to documentation on [the `act` function in React JS](https://reactjs.org/docs/test-utils.html#act).
+
+!!! info
+ In new test code, consider adopting libraries like [`dom-testing-library-lua`](https://github.com/Roblox/dom-testing-library-lua) and [`react-testing-library-lua`](https://github.com/Roblox/react-testing-library-lua), which are ports of JS testing libraries that handle much of the `act` logic for you. Reach out to #lua-foundation on slack for the status of these libraries.
+
+## Hooks
+
+While async rendering is a paradigm shift for under-the-hood rendering behavior, hooks are a paradigm shift for component development. They're designed to be a more performant, composable, testable, and ergonomic approach to defining stateful behavior and side effects (relative to to class component lifecycle methods).
+
+You can access hooks via the `React` Package. In order to encourage migration to the new package conventions, `RoactCompat` **does not** export the hooks API. Follow the [previous section](upgrading-to-roact-17.md#accessing-new-features) to set up your dependencies appropriately.
+
+An excellent and comprehensive guide for hooks can be found in the [React JS documentation](https://reactjs.org/docs/hooks-intro.html); the example below will help illustrate what they look like when used in Luau.
+
+### Hooks Example
+
+The following is a simple example that uses two of the most common hooks: `useState` and `useEffect`.
+
+```lua
+local React = require(Packages.React)
+
+function ClickerComponent(props)
+ local count, setCount = React.useState(0)
+ local function onClick()
+ setCount(function(oldCount)
+ return oldCount + 1
+ end)
+ end
+
+ React.useEffect(function()
+ print(string.format("You've clicked %d times!", count))
+ end)
+
+ return React.createElement("TextButton", {
+ Text = tostring(count),
+ [React.Event.Activated] = onClick,
+ })
+end
+```
+
+Check the [API Reference](../api-reference/react.md#reactusestate) to see the complete list of hooks supported by Roact 17.
+
+## Utilities
+
+### React.memo
+
+The `memo` function can be used to memoize a function component, returning the same element if the same props are provided. This may be a helpful optimization in scenarios where a function component frequently re-renders without changes due to its parent re-rendering.
+
+Refer to the [React JS documentation](https://reactjs.org/docs/react-api.html#reactmemo) for more details and examples.
+
+### React.Children
+
+*Under construction 🔨*
+
+## Roact-Only Features
+
+In addition to the new features provided by aligning with React JS 17, a few new features have been added to extend and improve support for legacy Roact features.
+
+### useBinding Hook
+
+Legacy Roact introduces a concept called "bindings", a shorthand for the concept of "unidirectional data bindings." You can learn more about bindings in the [legacy Roact documentation](https://roblox.github.io/roact/advanced/bindings-and-refs/#bindings).
+
+The legacy version of bindings were created similarly to refs, using a `createBinding` method that would be called in the `init` method of a class component. So how do we use bindings with function components?
+
+Roact 17 introduces, alongside the core hooks from React JS 17, an additional `useBinding` hook to address this case. You can use it much like a `useState` hook, except it will follow binding semantics: instead of triggering component re-renders, binding updates will directly update subscribed values.
+
+```lua
+local React = require(Packages.React)
+
+function ClickerComponent(props)
+ local count, setCount = React.useBinding(0)
+ local function onClick()
+ -- This will only update subscribed host properties, specifically the
+ -- Text field of the button that's rendered below
+ setCount(count:getValue() + 1)
+ end
+
+ React.useEffect(function()
+ print(string.format("You've clicked %d times!", count))
+ end)
+
+ return React.createElement("TextButton", {
+ Text = count:map(tostring),
+ [React.Event.Activated] = onClick,
+ })
+end
+```
+
+### React.Tag
+
+*Under Construction 🔨*
diff --git a/docs/migrating-from-1x/convert-legacy-conventions.md b/docs/migrating-from-1x/convert-legacy-conventions.md
new file mode 100644
index 00000000..755bcc32
--- /dev/null
+++ b/docs/migrating-from-1x/convert-legacy-conventions.md
@@ -0,0 +1,288 @@
+# Updating Conventions and APIs
+
+Almost all legacy Roact code can be patched to Roact 17 using the RoactCompat library. However, while RoactCompat is backwards compatible with with Legacy Roact, it does not export new Roact 17 features like hooks.
+
+When writing code exclusively for Roact 17, you should access the primary APIs of Roact 17 through the `React` and `ReactRoblox` APIs, instead of through `RoactCompat`.
+
+* `React` exposes:
+ * some core legacy Roact APIs unchanged (`createElement`, `createContext`, `Component`, `Change`/`Event`, etc.)
+ * some React-JS-aligned updates to other legacy Roact APIs (`createFragment` -> `Fragment`, `oneChild` -> `Children.only`, etc.)
+ * and several brand new APIs (`useState`, `useRef`, `memo`, `cloneElement`, etc.)
+* `ReactRoblox` exposes:
+ * the `createRoot` API, which replaces the `mount`/`update`/`unmount` APIs
+ * the `createPortal` API, which replaces the `Roact.Portal` component
+
+To reduce confusion, try to avoid using `RoactCompat` and `React`/`ReactRoblox` in the same file. If you are adding new Roact features to code that uses `RoactCompat` syntax, take the following steps to align conventions:
+
+1. Replace the `Roact` import with the `React` import
+2. Swap all uses of `Roact` with `React`
+3. If the module uses any `Roact.Portal` instances, import the `ReactRoblox` package and replace them with `ReactRoblox.createPortal`
+4. If the module uses `mount`/`update`/`unmount`, import the `ReactRoblox` package to replace them
+ * Replace `Roact.mount` with `ReactRoblox.createRoot` followed by `root:render`
+ * Replace `Roact.update` with subsequent calls to `root:render`
+ * Replace `Roact.unmount` by calling `root:unmount`
+5. Begin adopting new features exported from the `React` package as needed
+
+## API Conversions
+
+Roact 17 adopts the naming conventions and API shape of React JS wherever possible, with small deviations made to accommodate the distinct features of the Luau language. Using new naming conventions and APIs helps to align with the React JS ecosystem and reduce friction and mental overhead when troubleshooting or browsing documentation.
+
+**Even though Roact 17 is designed to be largely compatible with legacy Roact, updating these conventions when adopting new features is highly encouraged.**
+
+### Mounting, Updating, and Unmounting
+
+Legacy Roact uses three functions to manage the lifecycle of an entire Roact tree composed of numerous components. This is typically the entry point into Roact-managed UI.
+
+In Roact 17, the equivalent mechanisms use a new concept called a "root" to more clearly encapsulate this behavior.
+
+* `ReactRoblox.createRoot`
+* `ReactRoblox.createBlockingRoot`
+* `ReactRoblox.createLegacyRoot`
+
+Since the root needs to access a Roblox `Instance` in order to attach to it, these functions are exported through the `ReactRoblox` package, also known as a "renderer". A renderer takes the abstract descriptions of UI generated by React components and turns them into a concrete UI element tree. You can think of the `ReactRoblox` package as the semantic equivalent of the `react-dom` package in the React JS ecosystem.
+
+#### Legacy Roact
+```lua
+local PlayerGui = game:GetService("Players").LocalPlayer.PlayerGui
+local Roact = require(Packages.Roact)
+
+local roactTree = Roact.mount(Roact.createElement("TextLabel", {
+ Text = "Hello world!",
+}, PlayerGui)
+
+task.wait(3)
+
+roactTree = Roact.update(roactTree, Roact.createElement("TextLabel", {
+ Text = "Hello Roblox!",
+})
+
+task.wait(3)
+
+Roact.unmount(roactTree)
+```
+
+#### Roact 17
+```lua hl_lines="2-3 5-8 10-11 13 17 23"
+local PlayerGui = game:GetService("Players").LocalPlayer.PlayerGui
+local React = require(Packages.React)
+local ReactRoblox = require(Packages.ReactRoblox)
+
+-- Roact 17 roots will take full ownership of the instance provided to them,
+-- so we should not create a root using PlayerGui directly
+local container = Instance.new("Folder")
+container.Parent = PlayerGui
+
+local root = ReactRoblox.createRoot(container)
+root:render(Roact.createElement("TextLabel", {
+ Text = "Hello world!",
+})
+
+task.wait(3)
+
+root:render(Roact.createElement("TextLabel", {
+ Text = "Hello Roblox!",
+})
+
+task.wait(3)
+
+root:render(nil)
+```
+
+!!! warning
+ The `createBlockingRoot` and `createLegacyRoot` functions will opt out of concurrent rendering, a feature of Roact 17 that allows smooth, scalable UI by dividing units of work across multiple frames when large amounts of UI changes are needed. We always recommend using `createRoot` unless you know what you're doing.
+
+### Component Lifecycle Names
+
+Legacy Roact used simplified names for component lifecycle functions to alleviate redundancy when defining lifecycle methods with syntax like `function MyComponent:didMount() ... end`.
+
+However, Roact 17 prefers to align as close as possible to React JS to reduce friction when referring to resources from the React JS ecosystem (questions, tutorials, examples, documentation, etc.). It's recommended to update class component lifecycle names to match React JS.
+
+* `didMount` ➡ `componentDidMount`
+* `shouldUpdate` ➡ `shouldComponentUpdate`
+* `didUpdate` ➡ `componentDidUpdate`
+* `didMount` ➡ `componentDidMount`
+* `willUpdate` ➡ `UNSTABLE_componentWillUpdate`
+ * It's recommended to use the `UNSTABLE_` prefix here, since the `componentWillUpdate` method is deprecated
+* `willUnmount` ➡ `componentWillUnmount`
+
+### Reserved Keys
+
+Legacy Roact went out of its way to let all prop keys be valid for component developers to use. In Roact 17, a small subset of prop keys are reserved for internal use to make them simple to provide.
+
+* `[Roact.Ref]` becomes `ref`
+* `[Roact.Children]` becomes `children`
+
+You can index `props.children` to refer to the children provided to a component, and use `ref` as a key to an element to provide a ref to it.
+
+#### Legacy Roact
+```lua
+local FocusButton = Roact.Component:extend("FocusButton")
+
+function FocusButton:init()
+ self.ref = Roact.createRef()
+end
+
+function FocusButton:render()
+ return Roact.createElement("Button", {
+ Size = self.props.Size,
+ [Roact.Ref] = self.ref
+ }, self.props[Roact.Children])
+end
+
+function FocusButton:didMount()
+ GuiService.SelectedObject = self.ref.current
+end
+```
+
+#### Roact 17
+```lua hl_lines="10 11"
+local FocusButton = Roact.Component:extend("FocusButton")
+
+function FocusButton:init()
+ self.ref = Roact.createRef()
+end
+
+function FocusButton:render()
+ return Roact.createElement("Button", {
+ Size = self.props.Size,
+ ref = self.ref
+ }, self.props.children)
+end
+
+function FocusButton:didMount()
+ GuiService.SelectedObject = self.ref.current
+end
+```
+
+### Context.Consumer
+
+Legacy Roact treats the special `Context.Consumer` component generated by `createContext` more like a typical component with a single prop (`render`) as its prop interface. React JS, on the other hand, uses a slightly abbreviated structure.
+
+The `Context.Consumer` component expects _no_ props and a _single_ child, where the child is the same mapping function that would be provided as the `render` prop in legacy Roact.
+
+#### Legacy Roact
+```lua
+local ThemeContext = Roact.createContext(nil)
+
+-- ...
+
+local function Button(props)
+ return Roact.createElement(ThemeContext.Consumer, {
+ render = function(theme)
+ return Roact.createElement("TextButton", {
+ BackgroundColor3 = theme.ButtonColor,
+ Text = props.text,
+ [Roact.Event.Activated] = props.onActivated,
+ })
+ end
+ })
+end
+```
+
+#### Roact 17
+```lua hl_lines="6-7 14"
+local ThemeContext = Roact.createContext(nil)
+
+-- ...
+
+local function Button(props)
+ return Roact.createElement(ThemeContext.Consumer, nil,
+ function(theme)
+ return Roact.createElement("TextButton", {
+ BackgroundColor3 = theme.ButtonColor,
+ Text = props.text,
+ [Roact.Event.Activated] = props.onActivated,
+ })
+ end
+ )
+end
+```
+
+!!! info
+ Roact 17 has a compatibility layer that will allow either structure to work as expected. If you'd like to see warnings that will help you migrate, enable the [`__COMPAT_WARNINGS__` global](../configuration.md#compatwarnings).
+
+### React.Fragment
+
+Legacy Roact treats fragments as a special kind of rendered object, distinct from a Roact element, using the `Roact.createFragment` function.
+
+In Roact 17, however, fragments are nothing more than a special component type. Your component should return a single element with the `React.Fragment` component type, with the children provided as the element's children.
+
+#### Legacy Roact
+```lua
+local function LabeledButton(props)
+ return Roact.createFragment({
+ Button = Roact.createElement("ImageButton", {
+ Image = props.buttonIcon,
+ }),
+ Label = Roact.createElement("TextLabel", {
+ Text = props.labelText,
+ }),
+ })
+end
+```
+
+#### Roact 17
+```lua hl_lines="2"
+local function LabeledButton(props)
+ return React.createElement(React.Fragment, nil, {
+ Button = React.createElement("ImageButton", {
+ Image = props.buttonIcon,
+ }),
+ Label = React.createElement("TextLabel", {
+ Text = props.labelText,
+ }),
+ })
+end
+```
+
+### ReactRoblox.createPortal
+
+Legacy Roact uses a special `Roact.Portal` component to represent a portal. In Roact 17, we align with upstream and export the `ReactRoblox.createPortal` function.
+
+Portals are considered to be a feature with renderer-specific functionality since they attach directly to a host instance. Any APIs that interact directly with Roblox Instances will be exported via `ReactRoblox` in order to maintain the renderer abstraction.
+
+#### Legacy Roact
+```lua
+local PlayerGui = game:GetService("Players").LocalPlayer.PlayerGui
+
+local function Modal(props)
+ return Roact.createElement(Roact.Portal, {
+ target = PlayerGui,
+ }, {
+ Modal = Roact.createElement("ScreenGui", {}, {
+ Label = Roact.createElement("TextButton", {
+ Text = "Click me to close!",
+ [Roact.Event.Activated] = props.onClose,
+ })
+ })
+ })
+end
+```
+
+#### Roact 17
+```lua hl_lines="4 11"
+local PlayerGui = game:GetService("Players").LocalPlayer.PlayerGui
+
+local function Modal(props)
+ return ReactRoblox.createPortal({
+ Modal = Roact.createElement("ScreenGui", {}, {
+ Label = Roact.createElement("TextButton", {
+ Text = "Click me to close!",
+ [Roact.Event.Activated] = props.onClose,
+ })
+ })
+ }, PlayerGui)
+end
+```
+
+### Roact.oneChild
+
+Legacy Roact provides an infrequently-used utility function called `oneChild` that guarantees that the argument provided to it is a table with only one child Roact element. If the provided table contains zero elements or two or more elements, `oneChild` will throw an error.
+
+Roact 17 inherits a somewhat similar function from React JS: `React.Children.only`. This function will throw an error if the provided argument is not _itself_ a single child element. In other words, it expects a single child instead of a table containing only one child. This can be converted by unwrapping the provided children manually before passing them along to `React.Children.only`.
+
+Limiting a component to a single child isn't always necessary. Consider whether your component is capable of supporting an arbitrary number of children after all. In that case, you can simply remove the call to `Roact.oneChild`.
+
+## Eliminating RoactCompat
+
+Once you've completed all of the above conversions, you should no longer need to rely on the `RoactCompat` package in your module. Congratulations, your migration is complete!
\ No newline at end of file
diff --git a/docs/migrating-from-1x/minimum-requirements.md b/docs/migrating-from-1x/minimum-requirements.md
new file mode 100644
index 00000000..26f2c1f5
--- /dev/null
+++ b/docs/migrating-from-1x/minimum-requirements.md
@@ -0,0 +1,250 @@
+# Minimum Requirements
+
+When upgrading to Roact 17+, a small set of legacy patterns and features need to be fixed in order to maintain intended behavior. Once these conditions are met, your legacy Roact code should work as expected in Roact 17.
+
+**All of these requirements can be met using APIs available in legacy Roact**, though most will require a minimum version. This means that a Roact codebase can safely be made _compatible_ with Roact 17 as a preliminary step before adopting it.
+
+## No Reserved Props
+
+In Roact 17, components cannot rely on any reserved prop keywords:
+
+* "ref" - reserved by Roact to assign refs, equivalent to legacy Roact's `Roact.Ref`
+* "key" - reserved by Roact to assign stable keys to children
+* "children" - reserved by Roact as a special prop representing the children passed down to the component
+
+If your component is using "ref" or "key" as the name of one of its props, **those props will no longer be populated with a value in Roact 17+.**
+
+Additionally, if it's using "children" as the name of one of its props, **the value of the "children" prop will become the table of child elements instead of the value provided by the parent component in Roact 17+.**
+
+!!! info
+ This restriction does not involve legacy APIs, so this migration can be completed in codebases depending upon **any version** of legacy Roact.
+
+### Example
+
+Suppose we have a component `OptionButton` and a separate component `ButtonGroup` that uses it.
+
+The `OptionButton` component is using a prop called `key` to pass through to its LayoutOrder. In Roact 17, this prop will be nil because it will be consumed by Roact and used as a [stable key](https://reactjs.org/docs/lists-and-keys.html#keys).
+
+To fix this, we replace the use of the `key` prop with a prop with a different name.
+
+#### Legacy
+```lua
+local function OptionButton(props)
+ return Roact.createElement("TextButton", {
+ LayoutOrder = props.key,
+ Text = props.text,
+ [Roact.Event.Activated] = props.onClick,
+ })
+end
+
+local function ButtonGroup(props)
+ return Roact.createFragment({
+ CancelButton = Roact.createElement(OptionButton, {
+ key = 1,
+ text = "Cancel",
+ onClick = props.cancelCallback,
+ })
+ ConfirmButton = Roact.createElement(OptionButton, {
+ key = 2,
+ text = "Confirm",
+ onClick = props.confirmCallback,
+ })
+ })
+end
+```
+
+#### Roact 17 Compatible
+```lua hl_lines="3 12 17"
+local function OptionButton(props)
+ return Roact.createElement("TextButton", {
+ LayoutOrder = props.order,
+ Text = props.text,
+ [Roact.Event.Activated] = props.onClick,
+ })
+end
+
+local function ButtonGroup(props)
+ return Roact.createFragment({
+ CancelButton = Roact.createElement(OptionButton, {
+ order = 1,
+ text = "Cancel",
+ onClick = props.cancelCallback,
+ })
+ ConfirmButton = Roact.createElement(OptionButton, {
+ order = 2,
+ text = "Confirm",
+ onClick = props.confirmCallback,
+ })
+ })
+end
+```
+
+You can see a full example of a migration away from reserved keys in [this UIBlox PR](https://github.com/Roblox/uiblox/pull/368).
+
+## No Legacy Context
+
+Legacy Roact implemented a `_context` field on all component instances as an alternative implementation for the Context feature. This is deprecated in legacy Roact and is not supported in Roact 17+. **Attempting to access fields on `self._context` in Roact 17+ will throw an error.**
+
+### How To Convert
+
+Replace any uses of `_context` with the [Provider and Consumer pattern via `createContext`](https://roblox.github.io/roact/advanced/context/). This is the preferred pattern in legacy Roact as well because it allows Roact to trigger updates on context consumers when context providers pass in a new value.
+
+Generally, you'll take the following steps:
+
+1. Create a context object that you will use in place of `_context` by calling `createContext` and saving the result, usually as the return value of a separate `ModuleScript`.
+2. Wherever you have a component that _writes_ to `self._context`, instead wrap the component's children in a `Context.Provider` component and provide the value that was previously being written to `self._context`.
+3. Wherever you have a component that _reads_ from `self._context`, instead wrap that component's children in a `Context.Consumer` component. The Consumer accepts the `render` prop, which is a function that accepts a context value and returns a Roact element.
+
+!!! info
+ The `createContext` API is available in legacy **Roact 1.3.0** (or newer) and is fully supported in Roact 17.
+
+### Example
+
+Suppose we have a `style` object that must be provided to all children of our app. We define it in our top-level `App` component and read from it in our `Label` component.
+
+#### Legacy
+```lua
+local AppStyle = require(script.Parent.AppStyle)
+
+local Label = Roact.Component:extend("Label")
+
+function Label:init()
+ -- reading style from context
+ self.style = self._context.style
+end
+
+function Label:render()
+ return Roact.createElement("TextLabel", {
+ BackgroundColor3 = self.style.LabelColor,
+ Text = props.text,
+ })
+end
+
+local App = Roact.Component:extend("App")
+
+function App:init()
+ -- defining style in context
+ self._context.style = AppStyle
+end
+
+function App:render()
+ return Roact.createElement("Frame", {
+ Size = UDim2.fromScale(1, 1)
+ }, {
+ Start = Roact.createElement(Button, {
+ text = "Hello World",
+ })
+ })
+end
+```
+
+#### Roact 17 Compatible
+```lua hl_lines="3 8-9 11 14-15 21-24 31"
+local AppStyle = require(script.Parent.AppStyle)
+
+local StyleContext = Roact.createContext(nil)
+
+local Label = Roact.Component:extend("Label")
+
+function Label:render()
+ return Roact.createElement(StyleContext.Consumer, {
+ render = function(style)
+ return Roact.createElement("TextLabel", {
+ BackgroundColor3 = style.LabelColor,
+ Text = props.text,
+ })
+ end
+ })
+end
+
+local App = Roact.Component:extend("App")
+
+function App:render()
+ return Roact.createElement(StyleContext.Provider, {
+ value = AppStyle,
+ }, {
+ App = Roact.createElement("Frame", {
+ Size = UDim2.fromScale(1, 1)
+ }, {
+ Start = Roact.createElement(Button, {
+ text = "Hello World",
+ })
+ })
+ })
+end
+```
+
+You can see a full example of a migration to the `createContext` API in [this Lua Apps PR](https://github.com/Roblox/lua-apps/pull/3612).
+
+## Explicit Ref Forwarding
+
+Legacy Roact uses `Roact.Ref` as a special prop key to support the refs feature. Assigning the `[Roact.Ref]` property to a callback ref or ref object allows Roact to assign its value. However, Roact only interacts with the `Roact.Ref` property if the component receiving the props is a host component.
+
+Some class component definitions rely on this behavior by accepting and reassigning the `[Roact.Ref]` prop themselves, knowing that Roact won't capture it. The pattern of passing a provided ref onto a child is called "ref forwarding". We refer to using `[Roact.Ref]` as mechanism of ref forwarding as "implicit ref forwarding".
+
+### How To Convert
+
+In Roact 17+, `Roact.Ref` is aliased to the string "ref", and refs that point to class components are now supported. **Components that were using implicit ref forwarding will fail to forward their provided refs when upgrading to Roact 17+.**
+
+Fortunately, this can be easily fixed with the [`forwardRef` function](https://roblox.github.io/roact/advanced/bindings-and-refs/#ref-forwarding). We refer to this as "explicit ref forwarding".
+
+!!! info
+ The `forwardRef` API is available in legacy **Roact 1.4.0** (or newer) and is fully supported in Roact 17.
+
+### Example
+
+Suppose we have a `FancyTextBox` component that accepts a ref, and passes it on to an underlying `TextBox`. Rather than accepting the `[Roact.Ref]` prop, we should use the `Roact.forwardRef` wrapper to explicitly accept a ref and assign it to the `TextBox`.
+
+#### Legacy
+```lua
+local function FancyButton(props)
+ return Roact.createElement("TextBox", {
+ PlaceholderText = "Enter your text here",
+ PlaceholderColor3 = Color3.new(0.4, 0.4, 0.4),
+ [Roact.Change.Text] = props.onTextChange,
+ -- Implicitly forwarding a ref via the `Roact.Ref` prop
+ [Roact.Ref] = props[Roact.Ref],
+ })
+end
+```
+
+#### Roact 17 Compatible
+```lua hl_lines="1 6-7"
+local FancyButton = Roact.forwardRef(function(props, ref)
+ return Roact.createElement("TextBox", {
+ PlaceholderText = "Enter your text here",
+ PlaceholderColor3 = Color3.new(0.4, 0.4, 0.4),
+ [Roact.Change.Text] = props.onTextChange,
+ -- Explicitly forwarding a ref passed in via `forwardRef`
+ [Roact.Ref] = ref,
+ })
+end)
+```
+
+You can see a full example of `forwardRef` migration in [this UIBlox PR](https://github.com/Roblox/uiblox/pull/275).
+
+## Prefer getDerivedStateFromProps
+
+Legacy Roact allows class components to implement both `willUpdate` and `getDerivedStateFromProps` lifecycle methods.
+
+React JS, however, does not support both methods when implemented on the same component. When `getDerivedStateFromProps` is defined, it _replaces_ `componentWillUpdate` entirely. **Roact 17 inherits this restriction: `getDerivedStateFromProps` will replace `willUpdate` if both are defined.**
+
+### How To Convert
+
+In order to make existing components Roact 17 compatible, make sure to use _either_ `willUpdate` or `getDerivedStateFromProps`, but not both.
+
+Whenever possible, use `getDerivedStateFromProps` to resolve interactions between state and props. Just like in React JS 16.3.0 and onward, `willUpdate` is a deprecated legacy lifecycle method and should be avoided as it can exacerbate problems with asynchronous rendering, a flagship feature of Roact 17+.
+
+!!! info
+ The `getDerivedStateFromProps` static lifecycle method is supported in legacy Roact as far back as **Roact 0.2.0** and is fully supported in Roact 17.
+
+### Example
+
+Typically, usage of both `willUpdate` and `getDerivedStateFromProps` is a sign of an overly complicated component, which makes it difficult to provide a simplified example that's meaningful.
+
+Consider looking at other migrations of this kind for more complex examples:
+
+* [This PR to the legacy Infinite Scroller component](https://github.com/Roblox/infinite-scroller/pull/140).
+* [This PR to the Roact Gamepad library](https://github.com/Roblox/roact-gamepad/pull/53).
+
+Additionally, refer to the React JS guidance on [migrating away from legacy lifecycle methods](https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html).
diff --git a/docs/migrating-from-1x/upgrading-to-roact-17.md b/docs/migrating-from-1x/upgrading-to-roact-17.md
new file mode 100644
index 00000000..6a8be62b
--- /dev/null
+++ b/docs/migrating-from-1x/upgrading-to-roact-17.md
@@ -0,0 +1,109 @@
+# Adding a Roact 17 Dependency
+
+Roact 17 is available as a collection of packages hosted in the https://github.com/roblox/roact-alignment repository. Internal Roblox projects use a package manager called [Rotriever](https://github.com/roblox/rotriever) to resolve and manage dependencies.
+
+!!! info
+ Roact 17 is a "package workspace", meaning that it consists of multiple packages living in the same project. We recommend using Rotriever version [0.5.4](https://github.com/Roblox/rotriever/releases/tag/v0.5.4) or newer to make sure it has the latest workspace feature support.
+
+ Refer to [Rotriever's documentation](https://roblox.github.io/rotriever/installation/) for more information on how to install or upgrade your version.
+
+## New Projects
+
+1. Make sure you're using rotriever version `0.5.4` or newer. You can check this by running `rotrieve -V`.
+2. Add the following to your `rotriever.toml` manifest file:
+ ```toml
+ [dependencies]
+ React = "github.com/roblox/roact-alignment@17.0.1"
+ ReactRoblox = "github.com/roblox/roact-alignment@17.0.1"
+ ```
+3. Run `rotrieve install` to install all dependencies
+4. `React.lua` and `ReactRoblox.lua` will be added to the `Packages` folder generated by rotriever in your project. Make sure this folder is included in your project when testing with `roblox-cli` or Roblox Studio.
+
+## Replacing Legacy Roact
+
+The easiest way to adopt Roact 17 in an existing Roact project is to change your existing dependency on legacy Roact.
+
+Make the following changes to your `rotriever.toml` manifest file:
+```diff
+[dependencies]
+- Roact = "github.com/roblox/roact@1.4"
++ Roact = { target = "github.com/roblox/roact-alignment", version = "17.0.1", package = "RoactCompat" }
+```
+
+This creates a dependency on the `RoactCompat` package, which provides [a compatibility layer](../api-reference/roact-compat.md) for migrating from legacy APIs.
+
+Since it's still named `Roact` in the above snippet, it will be aliased to `Roact` in your code. If your code meets the [minimum requirements](minimum-requirements.md), this dependency change will upgrade your project to Roact 17 without requiring any further changes to your source code.
+
+### Accessing New Features
+
+However, the `RoactCompat` API only covers enough to provide backwards compatibility. To access new features like hooks or suspense, you will need to add dependencies on `React` and `ReactRoblox` as well:
+```toml
+[dependencies]
+Roact = { target = "github.com/roblox/roact-alignment", version = "17.0.1", package = "RoactCompat" }
+React = "github.com/roblox/roact-alignment@17.0.1"
+ReactRoblox = "github.com/roblox/roact-alignment@17.0.1"
+```
+
+The `RoactCompat` package **can be safely mixed and matched** with the new `React` and `ReactRoblox` packages; it's nothing more than a thin wrapper around them that aligns with the legacy Roact API and semantics.
+
+!!! info
+ Since we're not using an alias for the `React` and `ReactRoblox` packages, we can define our dependencies using the shorthand format. Read up on [specifying Rotriever dependencies](https://roblox.github.io/rotriever/guide/specifying-dependencies/) for more information.
+
+### Patching Other Dependencies
+
+You may need to additionally account for other dependencies in your project that use Roact. If you depend on a package that has its _own_ dependency on legacy Roact, you may need to use rotriever's `patch` feature to replace the legacy version throughout the tree.
+
+You might encounter this if you have existing dependencies on any of the following:
+
+* [InfiniteScroller](https://github.com/Roblox/infinite-scroller) (compatible with Roact 17 from version 0.8.0 onwards)
+* [RoactFitComponents](https://github.com/roblox/roact-fit-components) (compatible with Roact 17 from version 2.0.0 onwards)
+* [RoactRodux](https://github.com/Roblox/roact-rodux) (compatible with Roact 17 from version 0.4.1 onwards)
+* [UIBlox](https://github.com/Roblox/uiblox) (compatible with Roact 17 on the latest master)
+* Numerous other projects that depend on legacy Roact, especially if they're older than the latest version
+
+To resolve this, you can patch over any dependencies on legacy Roact and align them with your newly-added version. Add this additional section to your `rotriever.toml` manifest file:
+```toml
+[config.patch."github.com/roblox/roact"]
+Roact = { target = "github.com/roblox/roact-alignment", version = "17.0.1", package = "RoactCompat" }
+```
+
+To learn more about patching dependencies, check out [the Rotriever documentation](https://roblox.github.io/rotriever/guide/specifying-dependencies/#patching-dependencies).
+
+!!! caution
+ When you're patching over Roact, make sure your other dependencies are compatible with Roact 17. You may need to upgrade them to more recent versions so that they comply with the [Roact 17 requirements](minimum-requirements.md).
+
+## Testing with Both
+
+In some cases, you may not yet be equipped to safely adopt Roact 17, but you want to begin adopting it in tests or storybooks so your project can enforce compatibility.
+
+One reasonable way to accomplish this is to depend on _both_ projects, but to conditionally swap in Roact 17 at runtime. To do this, make sure your `rotriever.toml` manifest file contains the following:
+```toml
+[dependencies]
+Roact = "github.com/roblox/roact@1.4"
+
+[dev_dependencies]
+RoactCompat = "github.com/roblox/roact-alignment@17.0.1"
+```
+
+This declares a dependency on legacy Roact as well as a [dev dependency](https://roblox.github.io/rotriever/guide/specifying-dependencies/#development-dependencies) on Roact 17.
+
+In your test runner script or other equivalent entry point, check the value of a global (or flag, or any other preferred configuration method), and overwrite the `Roact` API with that of `RoactCompat` when enabled. It may look something like this:
+
+```lua
+if _G.__NEW_ROACT__ then
+ local Roact = require(Packages.Roact)
+ local RoactCompat = require(Packages.Dev.RoactCompat)
+
+ -- Overwrite the contents of the `Roact` package with that of the
+ -- RoactCompat package
+ for api, _ in Roact do
+ Roact[api] = RoactCompat[api]
+ end
+end
+
+-- Proceed with test setup...
+```
+
+In the above example, an additional test suite can be created for use with `lest` or via a `robloxdev-cli` command line invocation that sets the `__NEW_ROACT__` global to `true`.
+
+To see this technique in action, you can find a comprehensive example [in this PR to the UIBlox library](https://github.com/Roblox/uiblox/pull/309).
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 00000000..51b61b75
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1,3 @@
+mkdocs
+mkdocs-material
+pymdown-extensions
\ No newline at end of file
diff --git a/examples.project.json b/examples.project.json
index 652b34fc..efb044b3 100644
--- a/examples.project.json
+++ b/examples.project.json
@@ -7,6 +7,9 @@
"$className": "ReplicatedStorage",
"Packages": {
"$path": "Packages"
+ },
+ "DeveloperTools": {
+ "$className": "BindableEvent"
}
},
@@ -29,4 +32,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/examples/ProjectWorkspace/DeveloperTools.lua b/examples/ProjectWorkspace/DeveloperTools.lua
new file mode 100644
index 00000000..f60973b9
--- /dev/null
+++ b/examples/ProjectWorkspace/DeveloperTools.lua
@@ -0,0 +1,6 @@
+local ReplicatedStorage = game:GetService("ReplicatedStorage")
+
+-- FIXME: This is a bit hacky; should rotriever provide a smoother way to access
+-- dev dependencies?
+-- return require(ReplicatedStorage.Packages._Index["DeveloperTools"]["developer-tools"])
+return require(ReplicatedStorage.Packages._Index["DeveloperTools"]["DeveloperTools"])
diff --git a/examples/ProjectWorkspace/React.lua b/examples/ProjectWorkspace/React.lua
index fe34f482..ecd94ff6 100644
--- a/examples/ProjectWorkspace/React.lua
+++ b/examples/ProjectWorkspace/React.lua
@@ -3,4 +3,4 @@ local Workspace = ReplicatedStorage.Packages._Workspace
-- FIXME: This is a bit hacky; should rotriever provide a smoother way to access
-- local workspace members, even if they don't see use?
-return require(Workspace.React.React)
\ No newline at end of file
+return require(Workspace.React.React)
diff --git a/examples/ProjectWorkspace/ReactDevtoolsExtensions.lua b/examples/ProjectWorkspace/ReactDevtoolsExtensions.lua
new file mode 100644
index 00000000..fb674e36
--- /dev/null
+++ b/examples/ProjectWorkspace/ReactDevtoolsExtensions.lua
@@ -0,0 +1,5 @@
+local ReplicatedStorage = game:GetService("ReplicatedStorage")
+
+return require(
+ ReplicatedStorage.Packages._Workspace["ReactDevtoolsExtensions"]["ReactDevtoolsExtensions"]
+)
diff --git a/examples/ProjectWorkspace/ReactRoblox.lua b/examples/ProjectWorkspace/ReactRoblox.lua
index 4ce27307..44e783b3 100644
--- a/examples/ProjectWorkspace/ReactRoblox.lua
+++ b/examples/ProjectWorkspace/ReactRoblox.lua
@@ -3,4 +3,4 @@ local Workspace = ReplicatedStorage.Packages._Workspace
-- FIXME: This is a bit hacky; should rotriever provide a smoother way to access
-- local workspace members, even if they don't see use?
-return require(Workspace.ReactRoblox.ReactRoblox)
\ No newline at end of file
+return require(Workspace.ReactRoblox.ReactRoblox)
diff --git a/examples/binding/init.lua b/examples/binding/init.lua
index 8a737cea..8f3234f3 100644
--- a/examples/binding/init.lua
+++ b/examples/binding/init.lua
@@ -19,7 +19,7 @@ return function()
end
function BindingExample:init()
- self.binding, self.updateBinding = ReactRoblox.createBinding({})
+ self.binding, self.updateBinding = React.createBinding({})
end
function BindingExample:render()
@@ -67,4 +67,4 @@ return function()
end
return stop
-end
\ No newline at end of file
+end
diff --git a/examples/changed-signal/init.lua b/examples/changed-signal/init.lua
index 6a6d1b15..3e400f12 100644
--- a/examples/changed-signal/init.lua
+++ b/examples/changed-signal/init.lua
@@ -30,7 +30,7 @@ return function()
return React.createElement("TextLabel", {
LayoutOrder = layoutOrder,
Size = UDim2.new(1, 0, 0.5, 0),
- Text = "Reversed: " .. inputText:reverse(),
+ Text = "Reversed: " .. string.reverse(inputText),
})
end
diff --git a/examples/clock/init.lua b/examples/clock/init.lua
index c36d0a78..1747393b 100644
--- a/examples/clock/init.lua
+++ b/examples/clock/init.lua
@@ -46,4 +46,4 @@ return function()
end
return stop
-end
\ No newline at end of file
+end
diff --git a/examples/hello-roact/init.lua b/examples/hello-roact/init.lua
index d2935a25..56c5f844 100644
--- a/examples/hello-roact/init.lua
+++ b/examples/hello-roact/init.lua
@@ -25,4 +25,4 @@ return function()
end
return stop
-end
\ No newline at end of file
+end
diff --git a/examples/init.client.lua b/examples/init.client.lua
index a320546c..bc675f7c 100644
--- a/examples/init.client.lua
+++ b/examples/init.client.lua
@@ -1,5 +1,11 @@
local PlayerGui = game:GetService("Players").LocalPlayer:WaitForChild("PlayerGui")
+local DeveloperTools = require(script.ProjectWorkspace.DeveloperTools)
+local ReactDevtoolsExtensions = require(script.ProjectWorkspace.ReactDevtoolsExtensions)
+
+local inspector = DeveloperTools.forLibrary("ReactExamples")
+inspector:initRoact(ReactDevtoolsExtensions)
+
local exampleData = {
{
name = "hello-roact",
@@ -31,7 +37,7 @@ local exampleData = {
},
}
-for _, example in ipairs(exampleData) do
+for _, example in exampleData do
example.source = script:WaitForChild(example.name)
example.start = require(example.source)
end
@@ -98,8 +104,8 @@ function Examples.makeExampleList()
listLayout.SortOrder = Enum.SortOrder.LayoutOrder
listLayout.Parent = exampleList
- for index, example in ipairs(exampleData) do
- local label = ("%s\nexamples/%s"):format(example.label, example.name)
+ for index, example in exampleData do
+ local label = string.format("%s\nexamples/%s", example.label, example.name)
local exampleCard = Instance.new("TextButton")
exampleCard.Name = "Example: " .. example.name
@@ -131,4 +137,4 @@ function Examples.makeExampleList()
end
Examples.exampleList = Examples.makeExampleList()
-Examples.exampleList.Parent = PlayerGui
\ No newline at end of file
+Examples.exampleList.Parent = PlayerGui
diff --git a/examples/stress-test/init.lua b/examples/stress-test/init.lua
index 5662d0fa..d22a2753 100644
--- a/examples/stress-test/init.lua
+++ b/examples/stress-test/init.lua
@@ -6,7 +6,7 @@ return function()
local ReactRoblox = require(script.Parent.ProjectWorkspace.ReactRoblox)
local NODE_SIZE = 10
- local GRID_SIZE = 50
+ local GRID_SIZE = 70
--[[
A frame that changes its background color according to time and position props
@@ -78,7 +78,7 @@ return function()
local rootInstance = Instance.new("Folder")
rootInstance.Parent = PlayerGui
- local root = ReactRoblox.createBlockingRoot(rootInstance)
+ local root = ReactRoblox.createRoot(rootInstance)
root:render(app)
local function stop()
@@ -87,4 +87,4 @@ return function()
end
return stop
-end
\ No newline at end of file
+end
diff --git a/foreman.toml b/foreman.toml
index 376329cc..468f3234 100644
--- a/foreman.toml
+++ b/foreman.toml
@@ -1,6 +1,7 @@
[tools]
-rotrieve = { source = "roblox/rotriever", version = "=0.5.0-alpha.3" }
-rojo = { source = "rojo-rbx/rojo", version = "6.0.1" }
-selene = { source = "Kampfkarren/selene", version = "0.13" }
-stylua = { source = "JohnnyMorganz/StyLua", version = "0.9.3" }
+rotrieve = { source = "Roblox/rotriever", version = "=0.5.13-alpha.4" }
+rojo = { source = "Roblox/rojo-rbx-rojo", version = "7.2.1" }
+selene = { source = "Roblox/Kampfkarren-selene", version = "=0.28.0" }
+stylua = { source = "Roblox/JohnnyMorganz-StyLua", version = "=0.18.1" }
testez = { source = "roblox/testez", version = "0.3.2" }
+rbx-aged-cli = { source = "Roblox/rbx-aged-tool", version = "=5.7.9" }
diff --git a/js-to-lua.config.js b/js-to-lua.config.js
new file mode 100644
index 00000000..fc08841e
--- /dev/null
+++ b/js-to-lua.config.js
@@ -0,0 +1,76 @@
+module.exports = {
+ lastSync: {
+ ref: "12adaffef7105e2714f82651ea51936c563fe15c",
+ conversionToolVersion: "ffc56d3952bd9ce9ccecc900f6632b0a1c08f222"
+ },
+ upstream: {
+ owner: "facebook",
+ repo: "react",
+ primaryBranch: "main"
+ },
+ downstream: {
+ owner: "roblox",
+ repo: "roact-alignment",
+ primaryBranch: "master",
+ patterns: [
+ "**/*.lua"
+ ],
+ ignorePatterns: [
+ "Packages/**/*"
+ ]
+ },
+ renameFiles: [
+ [
+ (filename) => filename.includes("packages/"),
+ (filename) => filename.replace("packages/", "modules/")
+
+ ],
+ [
+ (filename) => filename.includes("modules/shared") && !filename.includes("modules/shared/src"),
+ (filename) => filename.replace("modules/shared", "modules/shared/src")
+
+ ],
+ [
+ (filename) => filename.includes("modules/react/src/ReactSharedInternals.lua"),
+ (filename) => filename.replace("modules/react/src/ReactSharedInternals.lua", "modules/shared/src/ReactSharedInternals/init.lua")
+ ],
+ [
+ (filename) => filename.includes("modules/react/src/ReactCurrentDispatcher.lua") || filename.includes("modules/react/src/ReactCurrentBatchConfig.lua") || filename.includes("modules/react/src/ReactCurrentActQueue.lua") || filename.includes("modules/react/src/ReactCurrentOwner.lua") || filename.includes("modules/react/src/ReactDebugCurrentFrame.lua"),
+ (filename) => filename.replace("modules/react/src/", "modules/shared/src/ReactSharedInternals/")
+ ],
+ [
+ (filename) => filename.includes("modules/react-reconciler/src/ReactFiberHostConfigWithNoHydration.lua"),
+ (filename) => filename.replace("modules/react-reconciler/src/ReactFiberHostConfigWithNoHydration.lua", "modules/shared/src/ReactFiberHostConfig/WithNoHydration.lua")
+ ],
+ [
+ (filename) => filename.includes("modules/react-reconciler/src/ReactFiberHostConfigWithNoPersistence.lua"),
+ (filename) => filename.replace("modules/react-reconciler/src/ReactFiberHostConfigWithNoPersistence.lua", "modules/shared/src/ReactFiberHostConfig/WithNoPersistence.lua")
+ ],
+ [
+ (filename) => filename.includes("modules/react-reconciler/src/ReactFiberHostConfigWithNoTestSelectors.lua"),
+ (filename) => filename.replace("modules/react-reconciler/src/ReactFiberHostConfigWithNoTestSelectors.lua", "modules/shared/src/ReactFiberHostConfig/WithNoTestSelectors.lua")
+ ],
+ [
+ (filename) => filename.includes("__tests__/") && !filename.includes("src/__tests__/") && !filename.includes("PropMarkers/__tests__/") && !filename.includes("client/__tests__/"),
+ (filename) => filename.replace("__tests__/", "src/__tests__/")
+ ],
+ [
+ (filename) => filename.endsWith("-test.lua"),
+ (filename) => filename.replace("-test.lua", ".spec.lua")
+
+ ],
+ [
+ (filename) => filename.endsWith("-test.internal.lua"),
+ (filename) => filename.replace("-test.internal.lua", "-internal.spec.lua")
+
+ ],
+ [
+ (filename) => filename.includes("scripts/jest/matchers"),
+ (filename) => filename.replace("scripts/jest/matchers", "WorkspaceStatic/jest/matchers")
+ ],
+ [
+ (filename) => filename.endsWith("fixtures/legacy-jsx-runtimes/setupTests.lua"),
+ (filename) => filename.replace("fixtures/legacy-jsx-runtimes/setupTests.lua", "WorkspaceStatic/jest/matchers/createConsoleMatcher.lua")
+ ],
+ ],
+}
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 00000000..576f0636
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,54 @@
+site_name: Roact Documentation
+site_url: https://roblox.github.io/roact-alignment/
+repo_name: Roblox/roact-alignment
+repo_url: https://github.com/Roblox/roact-alignment
+
+theme:
+ name: material
+ palette:
+ - media: "(prefers-color-scheme: light)"
+ primary: indigo
+ scheme: default
+ toggle:
+ icon: material/toggle-switch-off-outline
+ name: Switch to dark mode
+ - media: "(prefers-color-scheme: dark)"
+ primary: indigo
+ scheme: slate
+ toggle:
+ icon: material/toggle-switch
+ name: Switch to light mode
+
+plugins:
+ - search:
+ separator: '[\s\-\.]'
+
+nav:
+ - Home: index.md
+ - Deviations: deviations.md
+ - Configuration: configuration.md
+ - Migrating From 1.x:
+ - Minimum Requirements: migrating-from-1x/minimum-requirements.md
+ - Add Roact 17 Dependency: migrating-from-1x/upgrading-to-roact-17.md
+ - Adopt New Features: migrating-from-1x/adopt-new-features.md
+ - Convert Legacy Conventions: migrating-from-1x/convert-legacy-conventions.md
+ - API Reference:
+ - React: api-reference/react.md
+ - ReactRoblox: api-reference/react-roblox.md
+ - RoactCompat: api-reference/roact-compat.md
+ - Additional Libraries: api-reference/additional-libraries.md
+ - Benchmarks: bench.md
+
+extra_css:
+ - extra.css
+
+markdown_extensions:
+ - admonition
+ - codehilite:
+ guess_lang: false
+ - toc:
+ permalink: true
+ - pymdownx.superfences
+ # FIXME: Add this back when the tabbed extension is supported by docs-deploy
+ # - pymdownx.tabbed:
+ # alternate_style: false
diff --git a/modules/TestRunner/default.project.json b/modules/TestRunner/default.project.json
new file mode 100644
index 00000000..5d3bb4db
--- /dev/null
+++ b/modules/TestRunner/default.project.json
@@ -0,0 +1,8 @@
+{
+ "name": "TestRunner",
+ "tree": {
+ "Src": {
+ "$path": "src"
+ }
+ }
+}
diff --git a/modules/TestRunner/rotriever.toml b/modules/TestRunner/rotriever.toml
new file mode 100644
index 00000000..9595c610
--- /dev/null
+++ b/modules/TestRunner/rotriever.toml
@@ -0,0 +1,15 @@
+[package]
+name = "TestRunner"
+version = { workspace = true }
+author = "Roblox"
+content_root = "src"
+files = ["src/**"]
+
+[dev_dependencies]
+LuauPolyfill = { workspace = true }
+JestDiff = "github.com/roblox/jest-roblox@3"
+Jest = { workspace = true }
+JestGlobals = { workspace = true }
+JestReact = { path = "../jest-react" }
+ReactGlobals = { path = "../react-globals" }
+Scheduler = { path = "../scheduler" }
diff --git a/modules/TestRunner/src/init.lua b/modules/TestRunner/src/init.lua
new file mode 100644
index 00000000..a5647075
--- /dev/null
+++ b/modules/TestRunner/src/init.lua
@@ -0,0 +1 @@
+return {}
diff --git a/modules/example-app/README.md b/modules/example-app/README.md
new file mode 100644
index 00000000..286ea48f
--- /dev/null
+++ b/modules/example-app/README.md
@@ -0,0 +1,3 @@
+# Example App
+
+A small example app using Foundation. Primarily intended to make testing React features and tooling easier.
diff --git a/modules/example-app/StarterScript.lua b/modules/example-app/StarterScript.lua
new file mode 100644
index 00000000..a8f70e98
--- /dev/null
+++ b/modules/example-app/StarterScript.lua
@@ -0,0 +1,16 @@
+local CorePackages = game:GetService("CorePackages")
+local RunService = game:GetService("RunService")
+
+if RunService:IsServer() then
+ return
+end
+
+-- Set global flags and initialize devtools before React is ever used
+local ReactGlobals = require(CorePackages.ReactGlobals)
+ReactGlobals.__DEV__ = true
+ReactGlobals.__PROFILE__ = true
+
+local ReactDevtoolsCore = require(CorePackages.ReactDevtoolsCore)
+ReactDevtoolsCore.backend.connectToDevtools()
+
+require(CorePackages.ExampleApp)
diff --git a/modules/roblox-jest/default.project.json b/modules/example-app/default.project.json
similarity index 61%
rename from modules/roblox-jest/default.project.json
rename to modules/example-app/default.project.json
index 2deebd58..233b46b1 100644
--- a/modules/roblox-jest/default.project.json
+++ b/modules/example-app/default.project.json
@@ -1,5 +1,5 @@
{
- "name": "RobloxJest",
+ "name": "ExampleApp",
"tree": {
"$path": "src"
}
diff --git a/modules/example-app/rotriever.toml b/modules/example-app/rotriever.toml
new file mode 100644
index 00000000..c26a8ee4
--- /dev/null
+++ b/modules/example-app/rotriever.toml
@@ -0,0 +1,19 @@
+[package]
+name = "ExampleApp"
+version = { workspace = true }
+authors = { workspace = true }
+publish = false
+content_root = "src"
+files = ["*", "!**/__tests__/**"]
+
+[config]
+registry_index = true
+
+[dependencies]
+React = { path = "../react" }
+ReactRoblox = { path = "../react-roblox" }
+ReactDevtoolsCore = { path = "../react-devtools-core" }
+ReactGlobals = { path = "../react-globals" }
+
+Foundation = "1.42.0"
+ReactUtils = "1.6.0"
diff --git a/Packages/_Workspace/.robloxrc b/modules/jest-react/.robloxrc
similarity index 100%
rename from Packages/_Workspace/.robloxrc
rename to modules/jest-react/.robloxrc
diff --git a/modules/jest-react/rotriever.toml b/modules/jest-react/rotriever.toml
index a1dd31dc..ac258ff5 100644
--- a/modules/jest-react/rotriever.toml
+++ b/modules/jest-react/rotriever.toml
@@ -4,8 +4,10 @@ version = { workspace = true }
authors = { workspace = true }
publish = true
content_root = "src"
+files = ["*", "!**/__tests__/**"]
[dependencies]
LuauPolyfill = { workspace = true }
-JestRoblox = { workspace = true }
+JestGlobals = { workspace = true }
+ReactGlobals = { path = "../react-globals" }
Shared = { path = "../shared" }
diff --git a/modules/jest-react/src/JestReact.lua b/modules/jest-react/src/JestReact.lua
index 11d9e4e0..a55e0243 100644
--- a/modules/jest-react/src/JestReact.lua
+++ b/modules/jest-react/src/JestReact.lua
@@ -1,156 +1,230 @@
---[[
- Roblox upstream: https://github.com/facebook/react/blob/e0f89aa056de95afc4c23909fce3d91fefb7dec7/packages/jest-react/src/JestReact.js
-]]
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.2/packages/jest-react/src/JestReact.js
+--[[*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ ]]
local Packages = script.Parent.Parent
-local ReactSymbols = require(Packages.Shared).ReactSymbols
-local REACT_ELEMENT_TYPE = ReactSymbols.REACT_ELEMENT_TYPE
-local REACT_FRAGMENT_TYPE = ReactSymbols.REACT_FRAGMENT_TYPE
-local invariant = require(Packages.Shared).invariant
+local ReactGlobals = require(Packages.ReactGlobals)
local LuauPolyfill = require(Packages.LuauPolyfill)
-local jestExpect = require(Packages.JestRoblox).Globals.expect
-
+local Array = LuauPolyfill.Array
+-- ROBLOX deviation START: not used
+-- local Boolean = LuauPolyfill.Boolean
+-- ROBLOX deviation END
+local Object = LuauPolyfill.Object
+local exports = {}
+-- ROBLOX deviation START: fix import
+-- local JestGlobals = require(Packages.Dev.JestGlobals)
+local JestGlobals = require(Packages.JestGlobals)
+local expect = JestGlobals.expect
+-- ROBLOX deviation END
+-- ROBLOX deviation START: fix import
+-- local sharedReactSymbolsModule = require(Packages.shared.ReactSymbols)
+local sharedReactSymbolsModule = require(Packages.Shared).ReactSymbols
+-- ROBLOX deviation END
+local REACT_ELEMENT_TYPE = sharedReactSymbolsModule.REACT_ELEMENT_TYPE
+local REACT_FRAGMENT_TYPE = sharedReactSymbolsModule.REACT_FRAGMENT_TYPE
+-- ROBLOX deviation START: fix import
+-- local invariant = require(Packages.shared.invariant).default
+local invariant = require(Packages.Shared).invariant
+-- ROBLOX deviation END
+-- ROBLOX deviation START: predeclare variables
+local jsonChildToJSXChild, jsonChildrenToJSXChildren
+-- ROBLOX deviation END
local function captureAssertion(fn)
- -- Trick to use a TestEZ expectation matcher inside another Jest
- -- matcher. `fn` contains an assertion; if it throws, we capture the
- -- error and return it, so the stack trace presented to the user points
- -- to the original assertion in the test file.
- local ok, result = pcall(fn)
-
- if not ok then
- -- deviation: The message here will be a string with some extra info
- -- that's not helpful, so we trim it down a bit
- local subMessageIndex = string.find(result, " ")
- local message = string.sub(result, subMessageIndex + 1)
-
- return {
- pass = false,
- message = function()
- return message
- end,
- }
+ -- Trick to use a Jest matcher inside another Jest matcher. `fn` contains an
+ -- assertion; if it throws, we capture the error and return it, so the stack
+ -- trace presented to the user points to the original assertion in the
+ -- test file.
+ do --[[ ROBLOX COMMENT: try-catch block conversion ]]
+ -- ROBLOX deviation START: use pcall, format message
+ -- local ok, result, hasReturned = xpcall(function()
+ -- fn()
+ -- end, function(error_)
+ -- return {
+ -- pass = false,
+ -- message = function()
+ -- return error_.message
+ -- end,
+ -- },
+ -- true
+ -- end)
+ -- if hasReturned then
+ -- return result
+ -- end
+ local ok, result = pcall(fn)
+
+ if not ok then
+ local stringResult = tostring(result)
+ local subMessageIndex = string.find(stringResult, " ")
+ local message = string.sub(stringResult, subMessageIndex + 1)
+
+ return {
+ pass = false,
+ message = function()
+ return message
+ end,
+ }
+ end
+ -- ROBLOX deviation END
end
-
return { pass = true }
end
-
local function assertYieldsWereCleared(root)
- local Scheduler = root._Scheduler
- local actualYields = Scheduler.unstable_clearYields()
-
- invariant(#actualYields == 0, 'Log of yielded values is not empty. ' ..
- 'Call expect(ReactTestRenderer).unstable_toHaveYielded(...) first.')
+ local Scheduler = root._Scheduler
+ -- ROBLOX deviation START: use dot notation
+ -- local actualYields = Scheduler:unstable_clearYields()
+ local actualYields = Scheduler.unstable_clearYields()
+ -- ROBLOX deviation END
+ invariant(
+ -- ROBLOX deviation START: fix .length
+ -- actualYields.length == 0,
+ #actualYields == 0,
+ -- ROBLOX deviation END
+ "Log of yielded values is not empty. "
+ .. "Call expect(ReactTestRenderer).unstable_toHaveYielded(...) first."
+ )
end
-
-local jsonChildrenToJSXChildren
-
-local function jsonChildToJSXChild(jsonChild)
- if jsonChild == nil or typeof(jsonChild) == 'string' then
- return jsonChild
- else
- local jsxChildren = jsonChildrenToJSXChildren(jsonChild.children)
-
- return{
- ["$$typeof"] = REACT_ELEMENT_TYPE,
- type = jsonChild.type,
- key = nil,
- ref = nil,
- props = (function()
- if jsxChildren == nil then
- return jsonChild.props
- end
- return LuauPolyfill.Object.assign({children = jsxChildren},jsonChild.props)
- end)(),
- _owner = nil,
- _store = (function()
- if _G.__DEV__ then
- return{}
- end
-
- return nil
- end)(),
- }
- end
+local function unstable_toMatchRenderedOutput(root, expectedJSX)
+ assertYieldsWereCleared(root)
+ -- ROBLOX deviation START: use dot notation
+ -- local actualJSON = root:toJSON()
+ local actualJSON = root.toJSON()
+ -- ROBLOX deviation END
+ local actualJSX
+ if actualJSON == nil or typeof(actualJSON) == "string" then
+ actualJSX = actualJSON
+ -- ROBLOX deviation START: remove toJSBoolean
+ -- elseif Boolean.toJSBoolean(Array.isArray(actualJSON)) then
+ elseif Array.isArray(actualJSON) then
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: fix .length
+ -- if actualJSON.length == 0 then
+ if #actualJSON == 0 then
+ -- ROBLOX deviation END
+ actualJSX = nil
+ -- ROBLOX deviation START: fix .length
+ -- elseif actualJSON.length == 1 then
+ elseif #actualJSON == 1 then
+ -- ROBLOX deviation END
+ actualJSX = jsonChildToJSXChild(actualJSON[
+ 1 --[[ ROBLOX adaptation: added 1 to array index ]]
+ ])
+ else
+ local actualJSXChildren = jsonChildrenToJSXChildren(actualJSON)
+ if actualJSXChildren == nil or typeof(actualJSXChildren) == "string" then
+ actualJSX = actualJSXChildren
+ else
+ actualJSX = {
+ ["$$typeof"] = REACT_ELEMENT_TYPE,
+ type = REACT_FRAGMENT_TYPE,
+ key = nil,
+ ref = nil,
+ props = { children = actualJSXChildren },
+ _owner = nil,
+ -- ROBLOX deviation START: remove toJSBoolean, use ReactGlobals.__DEV__
+ -- _store = if Boolean.toJSBoolean(__DEV__) then {} else nil,
+ _store = if ReactGlobals.__DEV__ then {} else nil,
+ -- ROBLOX deviation END
+ }
+ end
+ end
+ else
+ actualJSX = jsonChildToJSXChild(actualJSON)
+ end
+ return captureAssertion(function()
+ expect(actualJSX).toEqual(expectedJSX)
+ end)
end
-
-jsonChildrenToJSXChildren = function(jsonChildren)
- if jsonChildren ~= nil then
- if #jsonChildren == 1 then
- return jsonChildToJSXChild(jsonChildren[1])
- elseif #jsonChildren > 1 then
- local jsxChildren = {}
- local allJSXChildrenAreStrings = true
- local jsxChildrenString = ''
-
- for _, jsonChild in ipairs(jsonChildren) do
- local jsxChild = jsonChildToJSXChild(jsonChild)
-
- table.insert(jsxChildren, jsxChild)
-
- if allJSXChildrenAreStrings then
- if typeof(jsxChild) == 'string' then
- jsxChildrenString = jsxChildrenString .. jsxChild
- elseif jsxChild ~= nil then
- allJSXChildrenAreStrings = false
- end
- end
- end
-
- if allJSXChildrenAreStrings then
- return jsxChildrenString
- end
-
- return jsxChildren
- end
- end
-
- return nil
+exports.unstable_toMatchRenderedOutput = unstable_toMatchRenderedOutput
+-- ROBLOX deviation START: predeclared function
+-- local function jsonChildToJSXChild(jsonChild)
+function jsonChildToJSXChild(jsonChild)
+ -- ROBLOX deviation END
+ if jsonChild == nil or typeof(jsonChild) == "string" then
+ return jsonChild
+ else
+ local jsxChildren = jsonChildrenToJSXChildren(jsonChild.children)
+ return {
+ ["$$typeof"] = REACT_ELEMENT_TYPE,
+ type = jsonChild.type,
+ key = nil,
+ ref = nil,
+ props = if jsxChildren == nil
+ then jsonChild.props
+ else Object.assign({}, jsonChild.props, { children = jsxChildren }),
+ _owner = nil,
+ -- ROBLOX deviation START: remove toJSBoolean, use ReactGlobals.__DEV__
+ -- _store = if Boolean.toJSBoolean(__DEV__) then {} else nil,
+ _store = if ReactGlobals.__DEV__ then {} else nil,
+ -- ROBLOX deviation END
+ }
+ end
end
-
-local function unstable_toMatchRenderedOutput(root, expectedJSX)
- assertYieldsWereCleared(root)
-
- local actualJSON = root.toJSON()
- local actualJSX
-
- if actualJSON == nil or typeof(actualJSON) == 'string' then
- actualJSX = actualJSON
- elseif LuauPolyfill.Array.isArray(actualJSON) then
- if #actualJSON == 0 then
- actualJSX = nil
- elseif #actualJSON == 1 then
- actualJSX = jsonChildToJSXChild(actualJSON[1])
- else
- local actualJSXChildren = jsonChildrenToJSXChildren(actualJSON)
-
- if actualJSXChildren == nil or typeof(actualJSXChildren) == 'string' then
- actualJSX = actualJSXChildren
- else
- actualJSX = {
- ["$$typeof"] = REACT_ELEMENT_TYPE,
- type = REACT_FRAGMENT_TYPE,
- key = nil,
- ref = nil,
- props = {children = actualJSXChildren},
- _owner = nil,
- _store = (function()
- if _G.__DEV__ then
- return{}
- end
-
- return nil
- end)(),
- }
- end
- end
- else
- actualJSX = jsonChildToJSXChild(actualJSON)
- end
-
- return captureAssertion(function()
- jestExpect(actualJSX).toEqual(expectedJSX)
- end)
+-- ROBLOX deviation START: predeclared function
+-- local function jsonChildrenToJSXChildren(jsonChildren)
+function jsonChildrenToJSXChildren(jsonChildren)
+ -- ROBLOX deviation END
+ if jsonChildren ~= nil then
+ -- ROBLOX deviation START: fix .length
+ -- if jsonChildren.length == 1 then
+ if #jsonChildren == 1 then
+ -- ROBLOX deviation END
+ return jsonChildToJSXChild(jsonChildren[
+ 1 --[[ ROBLOX adaptation: added 1 to array index ]]
+ ])
+ elseif
+ -- ROBLOX deviation START: fix .length
+ -- jsonChildren.length
+ #jsonChildren
+ -- ROBLOX deviation END
+ > 1 --[[ ROBLOX CHECK: operator '>' works only if either both arguments are strings or both are a number ]]
+ then
+ local jsxChildren = {}
+ local allJSXChildrenAreStrings = true
+ local jsxChildrenString = ""
+ -- ROBLOX deviation START: use in loop instead of while loop
+ -- do
+ -- local i = 0
+ -- while
+ -- i
+ -- < jsonChildren.length --[[ ROBLOX CHECK: operator '<' works only if either both arguments are strings or both are a number ]]
+ -- do
+ -- local jsxChild = jsonChildToJSXChild(jsonChildren[tostring(i)])
+ -- table.insert(jsxChildren, jsxChild) --[[ ROBLOX CHECK: check if 'jsxChildren' is an Array ]]
+ -- if Boolean.toJSBoolean(allJSXChildrenAreStrings) then
+ -- if typeof(jsxChild) == "string" then
+ -- jsxChildrenString += jsxChild
+ -- elseif jsxChild ~= nil then
+ -- allJSXChildrenAreStrings = false
+ -- end
+ -- end
+ -- i += 1
+ -- end
+ -- end
+ for _, jsonChild in jsonChildren do
+ local jsxChild = jsonChildToJSXChild(jsonChild)
+
+ table.insert(jsxChildren, jsxChild)
+
+ if allJSXChildrenAreStrings then
+ if typeof(jsxChild) == "string" then
+ jsxChildrenString = jsxChildrenString .. jsxChild
+ elseif jsxChild ~= nil then
+ allJSXChildrenAreStrings = false
+ end
+ end
+ end
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: remove toJSBoolean
+ -- return if Boolean.toJSBoolean(allJSXChildrenAreStrings)
+ return if allJSXChildrenAreStrings
+ -- ROBLOX deviation END
+ then jsxChildrenString
+ else jsxChildren
+ end
+ end
+ return nil
end
-
-return {
- unstable_toMatchRenderedOutput = unstable_toMatchRenderedOutput
-}
+return exports
diff --git a/modules/jest-react/src/getJestMatchers.roblox.lua b/modules/jest-react/src/getJestMatchers.roblox.lua
deleted file mode 100644
index 0d6ae558..00000000
--- a/modules/jest-react/src/getJestMatchers.roblox.lua
+++ /dev/null
@@ -1,52 +0,0 @@
---[[
- Roblox upstream: https://github.com/facebook/react/blob/69060e1da6061af845162dcf6854a5d9af28350a/scripts/jest/matchers/reactTestMatchers.js
-
- Note: this file is partially redundant with modules/scheduler/src/getJestMatchers.roblox.lua
- That is also happening upstream: https://github.com/facebook/react/blob/47ff31a77add22bef54aaed9d4fb62d5aa693afd/scripts/jest/matchers/schedulerTestMatchers.js
-]]
-local JestReact = require(script.Parent.JestReact)
-
-local function captureAssertion(fn)
- -- Trick to use a TestEZ expectation matcher inside another Jest
- -- matcher. `fn` contains an assertion; if it throws, we capture the
- -- error and return it, so the stack trace presented to the user points
- -- to the original assertion in the test file.
- local ok, result = pcall(fn)
-
- if not ok then
- return {
- pass = false,
- message = function()
- return tostring(result)
- end,
- }
- end
- return { pass = true }
-end
-
-return function(jestExpect)
- local function assertYieldsWereCleared(scheduler)
- local actualYields = scheduler.unstable_clearYields()
- if #actualYields ~= 0 then
- error("Log of yielded values is not empty. " ..
- "Call expectToHaveYielded(scheduler, ...) first.", 3)
- end
- end
-
- local function expectToMatchRenderedOutput(_matcherContext, ReactNoop, expectedJSX)
- if typeof(ReactNoop.getChildrenAsJSX) == 'function' then
- local Scheduler = ReactNoop._Scheduler;
- assertYieldsWereCleared(Scheduler);
- return captureAssertion(
- function()
- jestExpect(ReactNoop.getChildrenAsJSX()).toEqual(expectedJSX);
- end
- )
- end
- return JestReact.unstable_toMatchRenderedOutput(ReactNoop, expectedJSX)
- end
-
- return {
- toMatchRenderedOutput = expectToMatchRenderedOutput
- }
-end
diff --git a/modules/jest-react/src/init.lua b/modules/jest-react/src/init.lua
index ee810831..2ba651cb 100644
--- a/modules/jest-react/src/init.lua
+++ b/modules/jest-react/src/init.lua
@@ -1,4 +1,16 @@
--- ROBLOX FIXME: Clean up this package organization
-return {
- getJestMatchers = require(script["getJestMatchers.roblox"])
-}
\ No newline at end of file
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.2/packages/jest-react/index.js
+--[[*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ ]]
+local Packages = script.Parent
+local LuauPolyfill = require(Packages.LuauPolyfill)
+local Object = LuauPolyfill.Object
+local exports = {}
+-- ROBLOX deviation START: extract to variable, fix import and export type
+-- Object.assign(exports, require(script.src.JestReact))
+local jestReactModule = Object.assign(exports, require(script.JestReact))
+return exports :: typeof(exports) & typeof(jestReactModule)
+-- ROBLOX deviation END
diff --git a/modules/react-cache/.robloxrc b/modules/react-cache/.robloxrc
new file mode 100644
index 00000000..8f779576
--- /dev/null
+++ b/modules/react-cache/.robloxrc
@@ -0,0 +1,8 @@
+{
+ "language": {
+ "mode": "strict"
+ },
+ "lint": {
+ "*": "enabled"
+ }
+}
\ No newline at end of file
diff --git a/modules/react-cache/rotriever.toml b/modules/react-cache/rotriever.toml
index 0e230258..91554a93 100644
--- a/modules/react-cache/rotriever.toml
+++ b/modules/react-cache/rotriever.toml
@@ -3,16 +3,17 @@ name = "ReactCache"
version = { workspace = true }
authors = { workspace = true }
content_root = "src"
+files = ["*", "!**/__tests__/**"]
[dependencies]
+ReactGlobals = { path = "../react-globals" }
Scheduler = { path = "../scheduler" }
Shared = { path = "../shared" }
React = { path = "../react" }
+LuauPolyfill = { workspace = true }
[dev_dependencies]
-JestRoblox = { workspace = true }
-LuauPolyfill = { workspace = true }
+JestGlobals = { workspace = true }
Promise = { workspace = true }
JestReact = { path = "../jest-react" }
ReactTestRenderer = { path = "../react-test-renderer" }
-RobloxJest = { path = "../roblox-jest" }
diff --git a/modules/react-cache/src/LRU.lua b/modules/react-cache/src/LRU.lua
index ea694ad6..5907c8cd 100644
--- a/modules/react-cache/src/LRU.lua
+++ b/modules/react-cache/src/LRU.lua
@@ -1,151 +1,172 @@
--- Upstream: https://github.com/facebook/react/blob/faa697f4f9afe9f1c98e315b2a9e70f5a74a7a74/packages/react-cache/src/LRU.js
-
--- /**
--- * Copyright (c) Facebook, Inc. and its affiliates.
--- *
--- * This source code is licensed under the MIT license found in the
--- * LICENSE file in the root directory of this source tree.
--- *
--- * @flow
--- */
-type Object = { [string]: any? }
-
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.2/packages/react-cache/src/LRU.js
+--[[*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ ]]
local Packages = script.Parent.Parent
-local Scheduler = require(Packages.Scheduler)
+local LuauPolyfill = require(Packages.LuauPolyfill)
+type Object = LuauPolyfill.Object
local exports = {}
-
--- ROBLOX deviation: predeclare methods to fix declaration ordering problems
-local deleteLeastRecentlyUsedEntries
-local cleanUp
-
--- Intentionally not named imports because Rollup would
+-- ROBLOX deviation START: fix import
+-- local Scheduler = require(Packages.scheduler) -- Intentionally not named imports because Rollup would
+local Scheduler = require(Packages.Scheduler)
+-- ROBLOX deviation END
-- use dynamic dispatch for CommonJS interop named imports.
-local scheduleCallback = Scheduler.unstable_scheduleCallback
-local IdlePriority = Scheduler.unstable_IdlePriority
-
-type Entry = {
+local scheduleCallback, IdlePriority =
+ Scheduler.unstable_scheduleCallback, Scheduler.unstable_IdlePriority
+-- ROBLOX deviation START: use next_ instead
+-- type Entry = { value: T, onDelete: () -> unknown, previous: Entry, next: Entry }
+export type Entry = {
value: T,
- onDelete: () -> any,
+ onDelete: () -> ...unknown,
previous: Entry,
next_: Entry,
}
-
--- ROBLOX TODO: function generics
--- exports.createLRU = function(limit: number)
-exports.createLRU = function(limit: number)
- local LIMIT = limit
-
- -- Circular, doubly-linked list
- -- ROBLOX TODO: function generics
- local first: Entry | nil = nil
+-- ROBLOX deviation END
+local function createLRU(limit: number)
+ -- ROBLOX deviation START: add predeclared methods to fix declaration ordering problems
+ local deleteLeastRecentlyUsedEntries
+ local cleanUp
+ -- ROBLOX deviation END
+ local LIMIT = limit -- Circular, doubly-linked list
+ local first: Entry | nil --[[ ROBLOX CHECK: verify if `null` wasn't used differently than `undefined` ]] =
+ nil
local size: number = 0
-
local cleanUpIsScheduled: boolean = false
-
local function scheduleCleanUp()
- if cleanUpIsScheduled == false and size > LIMIT then
+ if
+ cleanUpIsScheduled == false
+ and size > LIMIT --[[ ROBLOX CHECK: operator '>' works only if either both arguments are strings or both are a number ]]
+ then
-- The cache size exceeds the limit. Schedule a callback to delete the
-- least recently used entries.
cleanUpIsScheduled = true
scheduleCallback(IdlePriority, cleanUp)
end
end
-
- cleanUp = function()
+ -- ROBLOX deviation START: predeclared function
+ -- local function cleanUp()
+ function cleanUp()
+ -- ROBLOX deviation END
cleanUpIsScheduled = false
deleteLeastRecentlyUsedEntries(LIMIT)
end
-
- deleteLeastRecentlyUsedEntries = function(targetSize: number)
+ -- ROBLOX deviation START: predeclared function
+ -- local function deleteLeastRecentlyUsedEntries(targetSize: number)
+ function deleteLeastRecentlyUsedEntries(targetSize: number)
+ -- ROBLOX deviation END
-- Delete entries from the cache, starting from the end of the list.
if first ~= nil then
- local resolvedFirst: Entry = first :: any
- local last = resolvedFirst.previous
- while size > targetSize and last ~= nil do
+ local resolvedFirst: Entry = first :: any
+ -- ROBLOX deviation START: explicitly type last
+ -- local last = resolvedFirst.previous
+ local last: Entry? = resolvedFirst.previous
+ -- ROBLOX deviation END
+ while
+ size > targetSize --[[ ROBLOX CHECK: operator '>' works only if either both arguments are strings or both are a number ]]
+ and last ~= nil
+ do
local onDelete = last.onDelete
local previous = last.previous
- last.onDelete = nil :: any
-
- -- Remove from the list
+ last.onDelete = nil :: any -- Remove from the list
+ -- ROBLOX deviation START: use next_ instead
+ -- last.next = nil :: any
+ -- last.previous = last.next
last.next_ = nil :: any
last.previous = last.next_
+ -- ROBLOX deviation END
if last == first then
-- Reached the head of the list.
last = nil
first = last
else
(first :: any).previous = previous
+ -- ROBLOX deviation START: use next_ instead
+ -- previous.next = first :: any
previous.next_ = first :: any
+ -- ROBLOX deviation END
last = previous
end
-
- size -= 1
-
- -- Call the destroy method after removing the entry from the list. If it
+ size -= 1 -- Call the destroy method after removing the entry from the list. If it
-- throws, the rest of cache will not be deleted, but it will be in a
-- valid state.
onDelete()
end
end
end
-
- -- ROBLOX TODO: function generics
- -- local function add(value: T, onDelete: () -> any): Entry
- local function add(value: any, onDelete: () -> any): Entry
- -- ROBLOX TODO: function generics
- local entry: Entry = {
+ local function add(value: Object, onDelete: () -> unknown): Entry
+ local entry = {
value = value,
onDelete = onDelete,
- next_ = nil,
- previous = nil,
- }
+ -- ROBLOX deviation START: use next_ instead
+ -- next = nil :: any,
+ next_ = nil :: any,
+ -- ROBLOX deviation END
+ previous = nil :: any,
+ -- ROBLOX deviation START: need to cast to Entry
+ -- }
+ } :: Entry
+ -- ROBLOX deviation END
if first == nil then
+ -- ROBLOX deviation START: use next_ instead
+ -- entry.next = entry
+ -- entry.previous = entry.next
entry.next_ = entry
entry.previous = entry.next_
+ -- ROBLOX deviation END
first = entry
else
-- Append to head
- -- ROBLOX FIXME: function generics, remove recast once Luau understands nil check
- local last = (first :: Entry).previous
+ local last = first.previous
+ -- ROBLOX deviation START: use next_ instead
+ -- last.next = entry
last.next_ = entry
- entry.previous = last;
- (first :: Entry).previous = entry
- entry.next_ = first :: Entry
-
+ -- ROBLOX deviation END
+ entry.previous = last
+ first.previous = entry
+ -- ROBLOX deviation START: use next_ instead
+ -- entry.next = first
+ entry.next_ = first
+ -- ROBLOX deviation END
first = entry
end
size += 1
return entry
end
-
- -- ROBLOX TODO: function generics
- -- local function update(entry: Entry, newValue: T): ()
- local function update(entry: Entry, newValue: any): ()
+ local function update(entry: Entry, newValue: T): ()
entry.value = newValue
end
-
- -- ROBLOX TODO: function generics
- -- local function access(entry: Entry): T
- local function access(entry: Entry): any
+ local function access(entry: Entry): T
+ -- ROBLOX deviation START: use next_ instead
+ -- local next_ = entry.next
local next_ = entry.next_
+ -- ROBLOX deviation END
if next_ ~= nil then
-- Entry already cached
- -- ROBLOX TODO: function generics
- local resolvedFirst: Entry = first :: Entry
+ local resolvedFirst: Entry = first :: any
if first ~= entry then
-- Remove from current position
local previous = entry.previous
+ -- ROBLOX deviation START: use next_ instead
+ -- previous.next = next_
previous.next_ = next_
- next_.previous = previous
-
- -- Append to head
+ -- ROBLOX deviation END
+ next_.previous = previous -- Append to head
local last = resolvedFirst.previous
+ -- ROBLOX deviation START: use next_ instead
+ -- last.next = entry
last.next_ = entry
+ -- ROBLOX deviation END
entry.previous = last
-
resolvedFirst.previous = entry
+ -- ROBLOX deviation START: use next_ instead
+ -- entry.next = resolvedFirst
entry.next_ = resolvedFirst
-
+ -- ROBLOX deviation END
first = entry
end
else
@@ -155,18 +176,11 @@ exports.createLRU = function(limit: number)
scheduleCleanUp()
return entry.value
end
-
local function setLimit(newLimit: number)
LIMIT = newLimit
scheduleCleanUp()
end
-
- return {
- add = add,
- update = update,
- access = access,
- setLimit = setLimit,
- }
+ return { add = add, update = update, access = access, setLimit = setLimit }
end
-
+exports.createLRU = createLRU
return exports
diff --git a/modules/react-cache/src/ReactCacheOld.lua b/modules/react-cache/src/ReactCacheOld.lua
index 5e5d8446..d925f991 100644
--- a/modules/react-cache/src/ReactCacheOld.lua
+++ b/modules/react-cache/src/ReactCacheOld.lua
@@ -1,61 +1,79 @@
--- Upstream: https://github.com/facebook/react/blob/faa697f4f9afe9f1c98e315b2a9e70f5a74a7a74/packages/react-cache/src/ReactCacheOld.js
-
--- /**
--- * Copyright (c) Facebook, Inc. and its affiliates.
--- *
--- * This source code is licensed under the MIT license found in the
--- * LICENSE file in the root directory of this source tree.
--- *
--- * @flow
--- */
-
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.2/packages/react-cache/src/ReactCacheOld.js
+--[[*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ ]]
local Packages = script.Parent.Parent
+local ReactGlobals = require(Packages.ReactGlobals)
+local LuauPolyfill = require(Packages.LuauPolyfill)
+-- ROBLOX deviation START: unnecessary import
+-- local Boolean = LuauPolyfill.Boolean
+-- ROBLOX deviation END
+-- ROBLOX deviation START: add inspect to print objects
+local inspect = LuauPolyfill.util.inspect
+-- ROBLOX deviation END
+-- ROBLOX deviation START: not needed
+-- local Error = LuauPolyfill.Error
+-- local Map = LuauPolyfill.Map
+-- ROBLOX deviation END
+-- ROBLOX deviation START: use console from React Shared
+-- local console = LuauPolyfill.console
local console = require(Packages.Shared).console
-local inspect = require(Packages.Shared).inspect.inspect
-type Map = { [K]: V }
-type Object = { [string]: any }
+-- ROBLOX deviation END
+type Map = LuauPolyfill.Map
+local exports = {}
+-- ROBLOX deviation START: add predeclared methods to fix declaration ordering
+local deleteEntry
+-- ROBLOX deviation END
+-- ROBLOX deviation START: fix import
+-- local sharedReactTypesModule = require(Packages.shared.ReactTypes)
+-- type Thenable = sharedReactTypesModule.Thenable
local ReactTypes = require(Packages.Shared)
+type Thenable = ReactTypes.Thenable
+-- ROBLOX deviation END
local React = require(Packages.React)
-type Thenable = ReactTypes.Thenable
-
--- ROBLOX deviation: predeclare methods to fix declaration ordering
-local deleteEntry
-
local createLRU = require(script.Parent.LRU).createLRU
-
--- ROBLOX deviation: use andThen convention, extra parameter for self
+-- ROBLOX deviation START: add this type in an attempt to tighten up the types to detect bugs found manually
+local LRU = require(script.Parent.LRU)
+type Entry = LRU.Entry
+type Record = { [K]: V }
+-- ROBLOX deviation END
+-- ROBLOX deviation START: change then to andThen
+-- type Suspender = { ["then"]: (resolve: () -> unknown, reject: () -> unknown) -> unknown } --[[ ROBLOX CHECK: inexact type upstream which is not supported by Luau. Verify if it doesn't break the analyze ]]
type Suspender = {
- andThen: (Object, () -> any, () -> any) -> any,
+ andThen: (
+ self: Suspender,
+ resolve: (...any) -> () | Suspender,
+ reject: (...any) -> () | Suspender
+ ) -> () | Suspender,
}
-
+-- ROBLOX deviation END
type PendingResult = {
- status: number,
+ status: number,--[[ ROBLOX NOTE: changed '0' to 'number' as Luau doesn't support numeric singleton types ]]
value: Suspender,
}
-
-type ResolvedResult = { status: number, value: V }
-
+type ResolvedResult = {
+ status: number,--[[ ROBLOX NOTE: changed '1' to 'number' as Luau doesn't support numeric singleton types ]]
+ value: V,
+}
type RejectedResult = {
- status: number,
- value: any,
+ status: number,--[[ ROBLOX NOTE: changed '2' to 'number' as Luau doesn't support numeric singleton types ]]
+ value: unknown,
}
-
type Result = PendingResult | ResolvedResult | RejectedResult
-
-type Resource = {
- read: (I) -> V,
- preload: (I) -> (),
-}
-
+type Resource = { read: (I) -> V, preload: (I) -> () } --[[ ROBLOX CHECK: inexact type upstream which is not supported by Luau. Verify if it doesn't break the analyze ]]
local Pending = 0
local Resolved = 1
local Rejected = 2
-
local ReactCurrentDispatcher =
+ -- ROBLOX deviation START: import from Shared package
+ -- React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher
require(Packages.Shared).ReactSharedInternals.ReactCurrentDispatcher
-
-local exports = {}
-
+-- ROBLOX deviation END
local function readContext(Context, observedBits)
local dispatcher = ReactCurrentDispatcher.current
if dispatcher == nil then
@@ -65,158 +83,251 @@ local function readContext(Context, observedBits)
.. "lifecycle methods."
)
end
- return dispatcher.readContext(Context, observedBits)
+ -- ROBLOX deviation START: use dot notation and additional cast as Luau doesn't narrow type to non-nil
+ -- return dispatcher:readContext(Context, observedBits)
+ return (dispatcher :: any).readContext(Context, observedBits)
+ -- ROBLOX deviation END
end
-
local function identityHashFn(input)
- if _G.__DEV__ then
+ -- ROBLOX deviation START: remove unnecessary toJSBoolean and use _G
+ -- if Boolean.toJSBoolean(__DEV__) then
+ if ReactGlobals.__DEV__ then
+ -- ROBLOX deviation END
if
typeof(input) ~= "string"
and typeof(input) ~= "number"
and typeof(input) ~= "boolean"
and input ~= nil
- and input ~= nil
+ -- ROBLOX deviation START: unnecessary duplicated condition - no difference between null and undefined
+ -- and input ~= nil
+ -- ROBLOX deviation END
then
console.error(
"Invalid key type. Expected a string, number, symbol, or boolean, "
.. "but instead received: %s"
.. "\n\nTo use non-primitive values as keys, you must pass a hash "
.. "function as the second argument to createResource().",
+ -- ROBLOX deviation START: use inspect to print object
+ -- input
inspect(input)
+ -- ROBLOX deviation END
)
end
end
return input
end
-
local CACHE_LIMIT = 500
local lru = createLRU(CACHE_LIMIT)
-
-local entries: Map, Map> = {}
-
+-- ROBLOX deviation START: tightened this up versus upstream to try and detect more bugs
+-- local entries: Map, Map> = Map.new()
+local entries: Record, Record>> = {}
+-- ROBLOX deviation END
local CacheContext = React.createContext(nil)
-
--- ROBLOX TODO: use function generics when they are unflagged
--- local function accessResult(
--- resource: any,
--- fetch: (I) -> Thenable,
--- input: I,
--- key: K
--- ): Result
-local function accessResult(
+local function accessResult(
resource: any,
- fetch: (any) -> Thenable,
- input: any,
- key: any
-): Result
+ fetch: (I) -> Thenable,
+ input: I,
+ key: K
+): Result
+ -- ROBLOX deviation START: use regular indexing instead
+ -- local entriesForResource = entries:get(resource)
local entriesForResource = entries[resource]
+ -- ROBLOX deviation END
if entriesForResource == nil then
+ -- ROBLOX deviation START: use table instead
+ -- entriesForResource = Map.new()
entriesForResource = {}
- entries[resource] = entriesForResource
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: cast as Luau doesn't narrow type on itself and use regular index setting
+ -- entries:set(resource, entriesForResource)
+ entries[resource] = entriesForResource :: Record>
+ -- ROBLOX deviation END
end
- local entry = entriesForResource[key]
+ -- ROBLOX deviation START: cast as Luau doesn't narrow type on itself
+ -- local entry = entriesForResource:get(key)
+ local entriesForResource_ = (
+ entriesForResource :: Record>
+ ) :: Record>
+ local entry = entriesForResource_[key]
+ -- ROBLOX deviation END
if entry == nil then
local thenable = fetch(input)
-
- -- ROBLOX deviation: reorder so newResult can be referenced in andThen()
+ -- ROBLOX deviation START: add reordered declaration so newResults so it can be referenced in andThen()
local newResult: PendingResult = {
status = Pending,
- value = thenable,
+ value = thenable :: any,
}
-
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: use andThen
+ -- thenable:then_(function(value)
thenable:andThen(function(value)
+ -- ROBLOX deviation END
if newResult.status == Pending then
- -- ROBLOX TODO: use function generics
- local resolvedResult: ResolvedResult = newResult :: any
+ local resolvedResult: ResolvedResult = newResult :: any
resolvedResult.status = Resolved
resolvedResult.value = value
end
- end, function(error_)
+ -- ROBLOX deviation START: explicit return type
+ -- end, function(error_)
+ end, function(error_): ()
+ -- ROBLOX deviation END
if newResult.status == Pending then
local rejectedResult: RejectedResult = newResult :: any
rejectedResult.status = Rejected
rejectedResult.value = error_
end
end)
-
- local newEntry = lru.add(newResult, function()
- return deleteEntry(resource, key)
+ -- ROBLOX deviation START: skip reordered code
+ -- local newResult: PendingResult = { status = Pending, value = thenable }
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: use dot notation
+ -- local newEntry = lru:add(newResult, function(...)
+ local newEntry = lru.add(newResult, function(...)
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: deleteEntry doesn't use 'self'
+ -- return deleteEntry(nil, resource, key, ...)
+ return deleteEntry(resource, key :: any)
+ -- ROBLOX deviation END
end)
- entriesForResource[key] = newEntry
+ -- ROBLOX deviation START: use casted variable as Luau doesn't narrow type on itself
+ -- entriesForResource:set(key, newEntry)
+ entriesForResource_[key] = newEntry
+ -- ROBLOX deviation END
return newResult
else
- return (lru.access(entry) :: any)
+ -- ROBLOX deviation START: use dot notation
+ -- return lru:access(entry) :: any
+ return lru.access(entry) :: any
+ -- ROBLOX deviation END
end
end
-
-deleteEntry = function(resource, key)
+-- ROBLOX deviation START: predeclared function
+-- local function deleteEntry(resource, key)
+function deleteEntry(resource, key): ()
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: use regular indexer
+ -- local entriesForResource = entries:get(resource)
local entriesForResource = entries[resource]
+ -- ROBLOX deviation END
if entriesForResource ~= nil then
+ -- ROBLOX deviation START: set property to nil instead
+ -- entriesForResource:delete(key)
entriesForResource[key] = nil
- if entriesForResource.size == 0 then
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: use # operator instead
+ -- if entriesForResource.size == 0 then
+ if #entriesForResource == 0 then
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: set property to nil instead
+ -- entries:delete(resource)
entries[resource] = nil
+ -- ROBLOX deviation END
end
end
end
-
--- ROBLOX TODO: function generics and constraints
--- export function unstable_createResource(
--- exports.unstable_createResource = function(
--- fetch: (I) -> Thenable,
--- maybeHashInput: ((I) -> K)?
--- ): Resource
-exports.unstable_createResource =
- function(
- fetch: (any) -> Thenable,
- maybeHashInput: ((any) -> any)?
- ): Resource
- local hashInput: (any) -> any
- if maybeHashInput ~= nil then
- -- ROBLOX TODO: remove recast once Luau understands nil check
- hashInput = maybeHashInput :: (any) -> any
- else
- hashInput = identityHashFn :: any
- end
-
- local resource
- resource = {
- -- ROBLOX TODO: function generics and constraints
- read = function(input: any): any
- -- react-cache currently doesn't rely on context, but it may in the
- -- future, so we read anyway to prevent access outside of render.
- readContext(CacheContext)
- local key = hashInput(input)
- -- ROBLOX TODO: function generics and constraints
- local result: Result = accessResult(resource, fetch, input, key)
- if result.status == Pending then
- local suspender = result.value
- error(suspender)
- elseif result.status == Resolved then
- local value = result.value
- return value
- elseif result.status == Rejected then
- local error_ = result.value
- error(error_)
- else
- -- Should be unreachable
- return nil :: any
- end
- end,
-
- -- ROBLOX TODO: function generics and constraints
- preload = function(input: any): ()
- -- react-cache currently doesn't rely on context, but it may in the
- -- future, so we read anyway to prevent access outside of render.
- readContext(CacheContext)
- local key = hashInput(input)
- accessResult(resource, fetch, input, key)
- end,
- }
- return resource
- end
-
-exports.unstable_setGlobalCacheLimit = function(limit: number)
+local function unstable_createResource<
+ I,
+ K, --[[ ROBLOX CHECK: upstream type uses type constraint which is not supported by Luau ]] --[[ K: string | number ]]
+ V
+>(
+ fetch: (
+ I
+ ) -> Thenable,
+ maybeHashInput: (
+ (I) -> K
+ )?
+): Resource<
+ I,
+ V
+>
+ local hashInput: (I) -> K = if maybeHashInput ~= nil
+ then maybeHashInput
+ else identityHashFn :: any
+ -- ROBLOX deviation START: split declaration and assignment
+ -- local resource = {
+ local resource
+ resource = {
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: no self param
+ -- read = function(self, input: I): V
+ read = function(input: I): V
+ -- ROBLOX deviation END
+ -- react-cache currently doesn't rely on context, but it may in the
+ -- future, so we read anyway to prevent access outside of render.
+ readContext(CacheContext)
+ local key = hashInput(input)
+ local result: Result = accessResult(resource, fetch, input, key)
+ -- ROBLOX deviation START: simplify switch statement conversion
+ -- repeat --[[ ROBLOX comment: switch statement conversion ]]
+ -- local entered_, break_ = false, false
+ -- local condition_ = result.status
+ -- for _, v in ipairs({ Pending, Resolved, Rejected }) do
+ -- if condition_ == v then
+ -- if v == Pending then
+ -- entered_ = true
+ -- do
+ -- local suspender = result.value
+ -- error(suspender)
+ -- end
+ -- end
+ -- if v == Resolved or entered_ then
+ -- entered_ = true
+ -- do
+ -- local value = result.value
+ -- return value
+ -- end
+ -- end
+ -- if v == Rejected or entered_ then
+ -- entered_ = true
+ -- do
+ -- local error_ = result.value
+ -- error(error_)
+ -- end
+ -- end
+ -- end
+ -- end
+ -- if not break_ then
+ -- -- Should be unreachable
+ -- return nil :: any
+ -- end
+ -- until true
+ if result.status == Pending then
+ local suspender = result.value
+ error(suspender)
+ elseif result.status == Resolved then
+ local value = result.value
+ -- ROBLOX deviation START: needs cast to narrow type
+ -- return value
+ return value :: V
+ -- ROBLOX deviation END
+ elseif result.status == Rejected then
+ local error_ = result.value
+ error(error_)
+ else
+ -- Should be unreachable
+ return nil :: any
+ end
+ -- ROBLOX deviation END
+ end,
+ -- ROBLOX deviation START: no self param
+ -- preload = function(self, input: I): ()
+ preload = function(input: I): ()
+ -- ROBLOX deviation END
+ -- react-cache currently doesn't rely on context, but it may in the
+ -- future, so we read anyway to prevent access outside of render.
+ readContext(CacheContext)
+ local key = hashInput(input)
+ accessResult(resource, fetch, input, key)
+ end,
+ }
+ return resource
+end
+exports.unstable_createResource = unstable_createResource
+local function unstable_setGlobalCacheLimit(limit: number)
+ -- ROBLOX deviation START: use dot notation
+ -- lru:setLimit(limit)
lru.setLimit(limit)
+ -- ROBLOX deviation END
end
-
+exports.unstable_setGlobalCacheLimit = unstable_setGlobalCacheLimit
return exports
diff --git a/modules/react-cache/src/__tests__/ReactCacheOld-internal.spec.lua b/modules/react-cache/src/__tests__/ReactCacheOld-internal.spec.lua
index bfab4e16..d3698dba 100644
--- a/modules/react-cache/src/__tests__/ReactCacheOld-internal.spec.lua
+++ b/modules/react-cache/src/__tests__/ReactCacheOld-internal.spec.lua
@@ -1,15 +1,34 @@
--- Upstream: https://github.com/facebook/react/blob/faa697f4f9afe9f1c98e315b2a9e70f5a74a7a74/packages/react-cache/src/__tests__/ReactCacheOld-test.internal.js
-
--- /**
--- * Copyright (c) Facebook, Inc. and its affiliates.
--- *
--- * This source code is licensed under the MIT license found in the
--- * LICENSE file in the root directory of this source tree.
--- *
--- * @emails react-core
--- */
-
--- !strict
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.2/packages/react-cache/src/__tests__/ReactCacheOld-test.internal.js
+--[[*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @emails react-core
+ ]]
+local Packages = script.Parent.Parent.Parent
+local ReactGlobals = require(Packages.ReactGlobals)
+local LuauPolyfill = require(Packages.LuauPolyfill)
+-- ROBLOX deviation START: unused imports
+-- local Array = LuauPolyfill.Array
+-- local Boolean = LuauPolyfill.Boolean
+-- local Error = LuauPolyfill.Error
+-- ROBLOX deviation END
+-- ROBLOX deviation START: add additional types
+type Error = LuauPolyfill.Error
+-- ROBLOX deviation END
+local setTimeout = LuauPolyfill.setTimeout
+-- ROBLOX deviation START: import promise from dev dependencies
+-- local Promise = require(Packages.Promise)
+local Promise = require(Packages.Dev.Promise)
+-- ROBLOX deviation END
+local JestGlobals = require(Packages.Dev.JestGlobals)
+local beforeEach = JestGlobals.beforeEach
+local describe = JestGlobals.describe
+local expect = JestGlobals.expect
+local it = JestGlobals.it
+local jest = JestGlobals.jest
local ReactCache
local createResource
@@ -20,388 +39,601 @@ local Scheduler
local Suspense
local TextResource
local textResourceShouldFail
-
-return function()
- local Packages = script.Parent.Parent.Parent
- local LuauPolyfill = require(Packages.Dev.LuauPolyfill)
- local Promise = require(Packages.Dev.Promise)
- local setTimeout = LuauPolyfill.setTimeout
- local jest = require(Packages.Dev.JestRoblox)
- local jestExpect = jest.Globals.expect
- local RobloxJest = require(Packages.Dev.RobloxJest)
- describe("ReactCache", function()
- beforeEach(function()
- RobloxJest.resetModules()
- RobloxJest.useFakeTimers()
-
- ReactFeatureFlags = require(Packages.Shared).ReactFeatureFlags
-
- ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false
- React = require(Packages.React)
- Suspense = React.Suspense
- ReactCache = require(script.Parent.Parent)
- createResource = ReactCache.unstable_createResource
- ReactTestRenderer = require(Packages.Dev.ReactTestRenderer)
- Scheduler = require(Packages.Scheduler)
-
- TextResource = createResource(function(input)
- local text = input[1]
- local ms = input[2] or 0
- local listeners = nil
- local status = "pending"
- local value = nil
- return {
- andThen = function(self, resolve, reject)
- if status == "pending" then
- if listeners == nil then
- listeners = { { resolve = resolve, reject = reject } }
- LuauPolyfill.setTimeout(function()
- if textResourceShouldFail then
- Scheduler.unstable_yieldValue(string.format("Promise rejected [%s]", text))
- status = "rejected"
- value = LuauPolyfill.Error("Failed to load: " .. text)
- for _, listener in ipairs(listeners) do
- listener.reject(value)
- end
- else
- Scheduler.unstable_yieldValue(string.format("Promise resolved [%s]", text))
- status = "resolved"
- value = text
- for _, listener in ipairs(listeners) do
- listener.resolve(value)
- end
+describe("ReactCache", function()
+ beforeEach(function()
+ jest.resetModules()
+ -- ROBLOX deviation START: add useFakeTimers call
+ jest.useFakeTimers()
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: fix require
+ -- ReactFeatureFlags = require_("shared/ReactFeatureFlags")
+ ReactFeatureFlags = require(Packages.Shared).ReactFeatureFlags
+ -- ROBLOX deviation END
+ ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false
+ -- ROBLOX deviation START: fix require
+ -- React = require_("react")
+ React = require(Packages.React)
+ -- ROBLOX deviation END
+ Suspense = React.Suspense
+ -- ROBLOX deviation START: fix require
+ -- ReactCache = require_("react-cache")
+ ReactCache = require(script.Parent.Parent)
+ -- ROBLOX deviation END
+ createResource = ReactCache.unstable_createResource
+ -- ROBLOX deviation START: fix requires
+ -- ReactTestRenderer = require_("react-test-renderer")
+ -- Scheduler = require_("scheduler")
+ ReactTestRenderer = require(Packages.Dev.ReactTestRenderer)
+ Scheduler = require(Packages.Scheduler)
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: explicit type
+ -- TextResource = createResource(function(ref0)
+ -- local text = ref0[1]
+ TextResource = createResource(function(ref0: { string | number })
+ local text = ref0[1] :: string
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: simplify
+ -- local ms = (function()
+ -- local element = table.unpack(ref0, 2, 2)
+ -- if element == nil then
+ -- return 0
+ -- else
+ -- return element
+ -- end
+ -- end)()
+ local ms = ref0[2] or 0
+ -- ROBLOX deviation END
+ local listeners = nil
+ local status = "pending"
+ -- ROBLOX deviation START: explicit type
+ -- local value = nil
+ local value = nil :: string | Error | nil
+ -- ROBLOX deviation END
+ return {
+ -- ROBLOX deviation START: use andThen
+ -- ["then"] = function(self, resolve, reject)
+ andThen = function(self, resolve, reject)
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: simplify switch statement conversion
+ -- repeat --[[ ROBLOX comment: switch statement conversion ]]
+ -- local condition_ = status
+ -- if condition_ == "pending" then
+ -- do
+ -- if listeners == nil then
+ -- listeners = { { resolve = resolve, reject = reject } }
+ -- setTimeout(function()
+ -- if
+ -- Boolean.toJSBoolean(textResourceShouldFail)
+ -- then
+ -- Scheduler:unstable_yieldValue(
+ -- ("Promise rejected [%s]"):format(
+ -- tostring(text)
+ -- )
+ -- )
+ -- status = "rejected"
+ -- value = Error.new(
+ -- "Failed to load: " .. tostring(text)
+ -- )
+ -- Array.forEach(listeners, function(listener)
+ -- return listener:reject(value)
+ -- end) --[[ ROBLOX CHECK: check if 'listeners' is an Array ]]
+ -- else
+ -- Scheduler:unstable_yieldValue(
+ -- ("Promise resolved [%s]"):format(
+ -- tostring(text)
+ -- )
+ -- )
+ -- status = "resolved"
+ -- value = text
+ -- Array.forEach(listeners, function(listener)
+ -- return listener:resolve(value)
+ -- end) --[[ ROBLOX CHECK: check if 'listeners' is an Array ]]
+ -- end
+ -- end, ms)
+ -- else
+ -- table.insert(
+ -- listeners,
+ -- { resolve = resolve, reject = reject }
+ -- ) --[[ ROBLOX CHECK: check if 'listeners' is an Array ]]
+ -- end
+ -- break
+ -- end
+ -- elseif condition_ == "resolved" then
+ -- do
+ -- resolve(value)
+ -- break
+ -- end
+ -- elseif condition_ == "rejected" then
+ -- do
+ -- reject(value)
+ -- break
+ -- end
+ -- end
+ -- until true
+ if status == "pending" then
+ if listeners == nil then
+ listeners = { { resolve = resolve, reject = reject } }
+ LuauPolyfill.setTimeout(function()
+ if textResourceShouldFail then
+ Scheduler.unstable_yieldValue(
+ string.format("Promise rejected [%s]", text)
+ )
+ status = "rejected"
+ value =
+ LuauPolyfill.Error.new("Failed to load: " .. text)
+ for _, listener in listeners do
+ listener.reject(value)
end
- end, ms)
- else
- table.insert(listeners, { resolve = resolve, reject = reject })
- end
- elseif status == "resolved" then
- resolve(value)
- elseif status == "rejected" then
- reject(value)
+ else
+ Scheduler.unstable_yieldValue(
+ string.format("Promise resolved [%s]", text)
+ )
+ status = "resolved"
+ value = text
+ for _, listener in listeners do
+ listener.resolve(value)
+ end
+ end
+ end, ms)
+ else
+ table.insert(
+ listeners,
+ { resolve = resolve, reject = reject }
+ )
end
- end,
- }
- end, function(input)
- return input[1]
- end)
-
- textResourceShouldFail = false
+ elseif status == "resolved" then
+ resolve(value)
+ elseif status == "rejected" then
+ reject(value)
+ end
+ -- ROBLOX deviation END
+ end,
+ }
+ end, function(ref0)
+ -- ROBLOX deviation START: ms not used
+ -- local text, ms = table.unpack(ref0, 1, 2)
+ -- return text
+ return ref0[1]
+ -- ROBLOX deviation END
end)
-
- local function Text(props)
- Scheduler.unstable_yieldValue(props.text)
- return props.text
- end
-
- local function AsyncText(props)
- local text = props.text
+ textResourceShouldFail = false
+ end)
+ local function Text(props)
+ -- ROBLOX deviation START: use dot notation
+ -- Scheduler:unstable_yieldValue(props.text)
+ Scheduler.unstable_yieldValue(props.text)
+ -- ROBLOX deviation END
+ return props.text
+ end
+ -- ROBLOX deviation START: explicit type
+ -- local function AsyncText(props)
+ local function AsyncText(props: { ms: number, text: string | number })
+ -- ROBLOX deviation END
+ local text = props.text
+ do --[[ ROBLOX COMMENT: try-catch block conversion ]]
+ -- ROBLOX deviation START: use pcall
+ -- local ok, result, hasReturned = xpcall(function()
local ok, result = pcall(function()
- TextResource.read({ props.text, props.ms })
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: use dot notation
+ -- TextResource:read({ props.text, props.ms })
+ -- Scheduler:unstable_yieldValue(text)
+ TextResource.read({ props.text :: string | number, props.ms })
Scheduler.unstable_yieldValue(text)
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: using pcall
+ -- return text, true
+ -- end, function(promise)
return text
end)
if not ok then
- if typeof(result.andThen) == "function" then
- Scheduler.unstable_yieldValue(string.format("Suspend! [%s]", text))
+ local promise = result
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: use andThen
+ -- if typeof(promise["then"]) == "function" then
+ if typeof(promise.andThen) == "function" then
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: use dot notation
+ -- Scheduler:unstable_yieldValue(
+ Scheduler.unstable_yieldValue(
+ -- ROBLOX deviation END
+ ("Suspend! [%s]"):format(tostring(text))
+ )
else
- Scheduler.unstable_yieldValue(string.format("Error! [%s]", text))
+ -- ROBLOX deviation START: use dot notation
+ -- Scheduler:unstable_yieldValue(("Error! [%s]"):format(tostring(text)))
+ Scheduler.unstable_yieldValue(("Error! [%s]"):format(tostring(text)))
+ -- ROBLOX deviation END
end
- error(result)
+ error(promise)
+ -- ROBLOX deviation START: using pcall
+ -- end)
+ -- if hasReturned then
+ -- return result
+ -- end
end
return result
+ -- ROBLOX deviation END
end
-
- it("throws a promise if the requested value is not in the cache", function()
- local function App()
- return (React.createElement(
- Suspense,
- { fallback = React.createElement(Text, { text = "Loading..." }) },
- { React.createElement(AsyncText, { ms = 100, text = "Hi" }) }
- ))
- end
-
- ReactTestRenderer.create(React.createElement(App), {
- unstable_isConcurrent = true,
- })
- -- Promise.delay(0):await()
-
- -- ROBLOX TODO: currently fails here with ReactCacheOld:178: attempt to index nil with 'status'
- -- return value from accessResult is nil
- jestExpect(Scheduler).toFlushAndYield({ "Suspend! [Hi]", "Loading..." })
-
- RobloxJest.advanceTimersByTime(100)
- jestExpect(Scheduler).toHaveYielded({ "Promise resolved [Hi]" })
- jestExpect(Scheduler).toFlushAndYield({ "Hi" })
- end)
-
- it("throws an error on the subsequent read if the promise is rejected", function()
+ end
+ it("throws a promise if the requested value is not in the cache", function()
+ local function App()
+ return React.createElement(
+ Suspense,
+ { fallback = React.createElement(Text, { text = "Loading..." }) },
+ React.createElement(AsyncText, { ms = 100, text = "Hi" })
+ )
+ end
+ ReactTestRenderer.create(
+ React.createElement(App, nil),
+ { unstable_isConcurrent = true }
+ )
+ expect(Scheduler).toFlushAndYield({ "Suspend! [Hi]", "Loading..." })
+ jest.advanceTimersByTime(100)
+ expect(Scheduler).toHaveYielded({ "Promise resolved [Hi]" })
+ expect(Scheduler).toFlushAndYield({ "Hi" })
+ end)
+ it("throws an error on the subsequent read if the promise is rejected", function()
+ return Promise.resolve():andThen(function()
local function App()
- return (React.createElement(
+ return React.createElement(
Suspense,
{ fallback = React.createElement(Text, { text = "Loading..." }) },
- { React.createElement(AsyncText, { ms = 100, text = "Hi" }) }
- ))
+ React.createElement(AsyncText, { ms = 100, text = "Hi" })
+ )
end
-
- local root = ReactTestRenderer.create(React.createElement(App), {
- unstable_isConcurrent = true,
- })
-
- jestExpect(Scheduler).toFlushAndYield({ "Suspend! [Hi]", "Loading..." })
-
+ local root = ReactTestRenderer.create(
+ React.createElement(App, nil),
+ { unstable_isConcurrent = true }
+ )
+ expect(Scheduler).toFlushAndYield({ "Suspend! [Hi]", "Loading..." })
textResourceShouldFail = true
- RobloxJest.advanceTimersByTime(100)
- jestExpect(Scheduler).toHaveYielded({ "Promise rejected [Hi]" })
-
- jestExpect(Scheduler).toFlushAndThrow("Failed to load: Hi")
- jestExpect(Scheduler).toHaveYielded({ "Error! [Hi]", "Error! [Hi]" })
-
- -- Should throw again on a subsequent read
- root.update(React.createElement(App))
- jestExpect(Scheduler).toFlushAndThrow("Failed to load: Hi")
- jestExpect(Scheduler).toHaveYielded({ "Error! [Hi]", "Error! [Hi]" })
+ jest.advanceTimersByTime(100)
+ expect(Scheduler).toHaveYielded({ "Promise rejected [Hi]" })
+ expect(Scheduler).toFlushAndThrow("Failed to load: Hi")
+ expect(Scheduler).toHaveYielded({ "Error! [Hi]", "Error! [Hi]" }) -- Should throw again on a subsequent read
+ -- ROBLOX deviation START: use dot notation
+ -- root:update(React.createElement(App, nil))
+ root.update(React.createElement(App, nil))
+ -- ROBLOX deviation END
+ expect(Scheduler).toFlushAndThrow("Failed to load: Hi")
+ expect(Scheduler).toHaveYielded({ "Error! [Hi]", "Error! [Hi]" })
end)
-
- it("warns if non-primitive key is passed to a resource without a hash function", function()
- local BadTextResource = createResource(function(input)
- local text = input[1]
- local ms = input[2] or 0
- return Promise.new(function(resolve, _reject)
- setTimeout(function()
+ end)
+ it(
+ "warns if non-primitive key is passed to a resource without a hash function",
+ function()
+ -- ROBLOX deviation START: explicit type
+ -- local BadTextResource = createResource(function(ref0)
+ -- local text = ref0[1]
+ local BadTextResource = createResource(function(ref0: { string | number })
+ local text = ref0[1] :: string
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: simplify
+ -- local ms = (function()
+ -- local element = table.unpack(ref0, 2, 2)
+ -- if element == nil then
+ -- return 0
+ -- else
+ -- return element
+ -- end
+ -- end)()
+ local ms = ref0[2] or 0
+ -- ROBLOX deviation END
+ return Promise.new(function(resolve, reject)
+ return setTimeout(function()
resolve(text)
end, ms)
end)
end)
-
local function App()
+ -- ROBLOX deviation START: use dot notation and cast type because luau doesn't support mixed arrays
+ -- Scheduler:unstable_yieldValue("App")
+ -- return BadTextResource:read({ "Hi", 100 })
Scheduler.unstable_yieldValue("App")
- return BadTextResource.read({ "Hi", 100 })
+ return BadTextResource.read({ "Hi" :: string | number, 100 })
+ -- ROBLOX deviation END
end
-
ReactTestRenderer.create(
React.createElement(
Suspense,
{ fallback = React.createElement(Text, { text = "Loading..." }) },
- { React.createElement(App) }
+ React.createElement(App, nil)
),
{ unstable_isConcurrent = true }
)
-
- if _G.__DEV__ then
- jestExpect(function()
- jestExpect(Scheduler).toFlushAndYield({ "App", "Loading..." })
- end).toErrorDev(
- "Warning: " -- ROBLOX FIXME: remove the Warning: prefix in consoleWithStackDev
- .. "Invalid key type. Expected a string, number, symbol, or "
- -- ROBLOX TODO: make console polyfill format arrays the same as JS
- .. 'boolean, but instead received: { "Hi", 100 }\n\n'
+ -- ROBLOX deviation START: remove toJSBoolean and use _G
+ -- if Boolean.toJSBoolean(__DEV__) then
+ if ReactGlobals.__DEV__ then
+ -- ROBLOX deviation END
+ expect(function()
+ expect(Scheduler).toFlushAndYield({ "App", "Loading..." })
+ end).toErrorDev({
+ "Invalid key type. Expected a string, number, symbol, or "
+ -- ROBLOX deviation START: FIXME - make console polyfill format arrays the same as JS
+ -- .. "boolean, but instead received: Hi,100\n\n"
+ .. 'boolean, but instead received: ["Hi", 100]\n\n'
+ -- ROBLOX deviation END
.. "To use non-primitive values as keys, you must pass a hash "
- .. "function as the second argument to createResource()."
- )
+ .. "function as the second argument to createResource().",
+ })
else
- jestExpect(Scheduler).toFlushAndYield({ "App", "Loading..." })
+ expect(Scheduler).toFlushAndYield({ "App", "Loading..." })
end
- end)
-
- it("evicts least recently used values", function()
+ end
+ )
+ it("evicts least recently used values", function()
+ return Promise.resolve():andThen(function()
+ -- ROBLOX deviation START: use dot notation
+ -- ReactCache:unstable_setGlobalCacheLimit(3) -- Render 1, 2, and 3
ReactCache.unstable_setGlobalCacheLimit(3)
-
- -- Render 1, 2, and 3
+ -- ROBLOX deviation END
local root = ReactTestRenderer.create(
- React.createElement(Suspense, { fallback = React.createElement(Text, { text = "Loading..." }) }, {
+ React.createElement(
+ Suspense,
+ { fallback = React.createElement(Text, { text = "Loading..." }) },
React.createElement(AsyncText, { ms = 100, text = 1 }),
React.createElement(AsyncText, { ms = 100, text = 2 }),
- React.createElement(AsyncText, { ms = 100, text = 3 }),
- }),
+ React.createElement(AsyncText, { ms = 100, text = 3 })
+ ),
{ unstable_isConcurrent = true }
)
- jestExpect(Scheduler).toFlushAndYield({
+ expect(Scheduler).toFlushAndYield({
"Suspend! [1]",
"Suspend! [2]",
"Suspend! [3]",
"Loading...",
})
- RobloxJest.advanceTimersByTime(100)
- jestExpect(Scheduler).toHaveYielded({
+ jest.advanceTimersByTime(100)
+ expect(Scheduler).toHaveYielded({
"Promise resolved [1]",
"Promise resolved [2]",
"Promise resolved [3]",
})
- jestExpect(Scheduler).toFlushAndYield({ 1, 2, 3 })
- jestExpect(root).toMatchRenderedOutput("123")
-
- -- Render 1, 4, 5
+ expect(Scheduler).toFlushAndYield({ 1, 2, 3 })
+ expect(root).toMatchRenderedOutput("123") -- Render 1, 4, 5
+ -- ROBLOX deviation START: use dot notation
+ -- root:update(
root.update(
- React.createElement(Suspense, { fallback = React.createElement(Text, { text = "Loading..." }) }, {
+ -- ROBLOX deviation END
+ React.createElement(
+ Suspense,
+ { fallback = React.createElement(Text, { text = "Loading..." }) },
React.createElement(AsyncText, { ms = 100, text = 1 }),
React.createElement(AsyncText, { ms = 100, text = 4 }),
- React.createElement(AsyncText, { ms = 100, text = 5 }),
- })
+ React.createElement(AsyncText, { ms = 100, text = 5 })
+ )
)
-
- jestExpect(Scheduler).toFlushAndYield({
- 1,
+ expect(Scheduler).toFlushAndYield({
+ -- ROBLOX deviation START: Luau doesn't support mixed arrays
+ -- 1,
+ 1 :: number | string,
+ -- ROBLOX deviation END
"Suspend! [4]",
"Suspend! [5]",
"Loading...",
})
- RobloxJest.advanceTimersByTime(100)
- jestExpect(Scheduler).toHaveYielded({
+ jest.advanceTimersByTime(100)
+ expect(Scheduler).toHaveYielded({
"Promise resolved [4]",
"Promise resolved [5]",
})
- jestExpect(Scheduler).toFlushAndYield({ 1, 4, 5 })
- jestExpect(root).toMatchRenderedOutput("145")
-
- -- We've now rendered values 1, 2, 3, 4, 5, over our limit of 3. The least
+ expect(Scheduler).toFlushAndYield({ 1, 4, 5 })
+ expect(root).toMatchRenderedOutput("145") -- We've now rendered values 1, 2, 3, 4, 5, over our limit of 3. The least
-- recently used values are 2 and 3. They should have been evicted.
-
+ -- ROBLOX deviation START: use dot notation
+ -- root:update(
root.update(
- React.createElement(Suspense, { fallback = React.createElement(Text, { text = "Loading..." }) }, {
+ -- ROBLOX deviation END
+ React.createElement(
+ Suspense,
+ { fallback = React.createElement(Text, { text = "Loading..." }) },
React.createElement(AsyncText, { ms = 100, text = 1 }),
React.createElement(AsyncText, { ms = 100, text = 2 }),
- React.createElement(AsyncText, { ms = 100, text = 3 }),
- })
+ React.createElement(AsyncText, { ms = 100, text = 3 })
+ )
)
-
- jestExpect(Scheduler).toFlushAndYield({
+ expect(Scheduler).toFlushAndYield({
-- 1 is still cached
- 1,
+ -- ROBLOX deviation START: Luau doesn't support mixed arrays
+ -- 1,
+ 1 :: number | string,
+ -- ROBLOX deviation END
-- 2 and 3 suspend because they were evicted from the cache
"Suspend! [2]",
"Suspend! [3]",
"Loading...",
})
- RobloxJest.advanceTimersByTime(100)
- jestExpect(Scheduler).toHaveYielded({
+ jest.advanceTimersByTime(100)
+ expect(Scheduler).toHaveYielded({
"Promise resolved [2]",
"Promise resolved [3]",
})
- jestExpect(Scheduler).toFlushAndYield({ 1, 2, 3 })
- jestExpect(root).toMatchRenderedOutput("123")
+ expect(Scheduler).toFlushAndYield({ 1, 2, 3 })
+ expect(root).toMatchRenderedOutput("123")
end)
-
- it("preloads during the render phase", function()
+ end, 9999999999)
+ it("preloads during the render phase", function()
+ return Promise.resolve():andThen(function()
local function App()
+ -- ROBLOX deviation START: use dot notation
+ -- TextResource:preload({ "B", 1000 })
+ -- TextResource:read({ "A", 1000 })
+ -- TextResource:read({ "B", 1000 })
TextResource.preload({ "B", 1000 })
TextResource.read({ "A", 1000 })
TextResource.read({ "B", 1000 })
+ -- ROBLOX deviation END
return React.createElement(Text, { text = "Result" })
end
-
local root = ReactTestRenderer.create(
React.createElement(
Suspense,
{ fallback = React.createElement(Text, { text = "Loading..." }) },
- { React.createElement(App) }
+ React.createElement(App, nil)
),
{ unstable_isConcurrent = true }
)
-
- jestExpect(Scheduler).toFlushAndYield({ "Loading..." })
-
- RobloxJest.advanceTimersByTime(1000)
- jestExpect(Scheduler).toHaveYielded({
+ expect(Scheduler).toFlushAndYield({ "Loading..." })
+ jest.advanceTimersByTime(1000)
+ expect(Scheduler).toHaveYielded({
"Promise resolved [B]",
"Promise resolved [A]",
})
- jestExpect(Scheduler).toFlushAndYield({ "Result" })
- jestExpect(root).toMatchRenderedOutput("Result")
+ expect(Scheduler).toFlushAndYield({ "Result" })
+ expect(root).toMatchRenderedOutput("Result")
end)
-
- it("if a thenable resolves multiple times, does not update the first cached value", function()
+ end)
+ it(
+ "if a thenable resolves multiple times, does not update the first cached value",
+ function()
local resolveThenable
- local BadTextResource = createResource(function(props)
- local _text = props.text
- local _ms = props.ms or 0
+ local BadTextResource = createResource(function(ref0)
+ -- ROBLOX deviation START: unused
+ -- local text = ref0[1]
+ -- local ms = (function()
+ -- local element = table.unpack(ref0, 2, 2)
+ -- if element == nil then
+ -- return 0
+ -- else
+ -- return element
+ -- end
+ -- end)()
+ -- ROBLOX deviation END
local listeners = nil
local value = nil
return {
+ -- ROBLOX deviation START: use andThen
+ -- ["then"] = function(self, resolve, reject)
andThen = function(self, resolve, reject)
+ -- ROBLOX deviation END
if value ~= nil then
resolve(value)
else
if listeners == nil then
listeners = { resolve }
resolveThenable = function(v)
- for _, listener in pairs(listeners) do
+ -- ROBLOX deviation START: use for..in loop instead
+ -- Array.forEach(listeners, function(listener)
+ -- return listener(v)
+ -- end) --[[ ROBLOX CHECK: check if 'listeners' is an Array ]]
+ for _, listener in listeners do
listener(v)
end
+ -- ROBLOX deviation END
end
else
- table.insert(listeners, resolve)
+ table.insert(listeners, resolve) --[[ ROBLOX CHECK: check if 'listeners' is an Array ]]
end
end
end,
}
- end, function(input)
- return input[1]
+ -- ROBLOX deviation START: explicit type
+ -- end, function(ref0)
+ end, function(ref0: { any })
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: ms not used
+ -- local text, ms = table.unpack(ref0, 1, 2)
+ -- return text
+ return ref0[1]
+ -- ROBLOX deviation END
end)
-
local function BadAsyncText(props)
local text = props.text
- local ok, result = pcall(function()
- local actualText = BadTextResource.read({ props.text, props.ms })
- Scheduler.unstable_yieldValue(actualText)
- return actualText
- end)
-
- if not ok then
- if typeof(result.andThen) == "function" then
- Scheduler.unstable_yieldValue(string.format("Suspend! [%s]", text))
- else
- Scheduler.unstable_yieldValue(string.format("Error! [%s]", text))
+ do --[[ ROBLOX COMMENT: try-catch block conversion ]]
+ -- ROBLOX deviation START: use pcall
+ -- local ok, result, hasReturned = xpcall(function()
+ local ok, result = pcall(function()
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: use dot notation
+ -- local actualText = BadTextResource:read({ props.text, props.ms })
+ -- Scheduler:unstable_yieldValue(actualText)
+ local actualText = BadTextResource.read({ props.text, props.ms })
+ Scheduler.unstable_yieldValue(actualText)
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: using pcall
+ -- return actualText, true
+ -- end, function(promise)
+ return actualText
+ end)
+ if not ok then
+ local promise = result
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: use andThen
+ -- if typeof(promise["then"]) == "function" then
+ if typeof(promise.andThen) == "function" then
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: use dot notation
+ -- Scheduler:unstable_yieldValue(
+ Scheduler.unstable_yieldValue(
+ -- ROBLOX deviation END
+ ("Suspend! [%s]"):format(tostring(text))
+ )
+ else
+ -- ROBLOX deviation START: use dot notation
+ -- Scheduler:unstable_yieldValue(
+ Scheduler.unstable_yieldValue(
+ -- ROBLOX deviation END
+ ("Error! [%s]"):format(tostring(text))
+ )
+ end
+ error(promise)
+ -- ROBLOX deviation START: using pcall
+ -- end)
+ -- if hasReturned then
+ -- return result
+ -- end
end
- error(result)
+ return result
+ -- ROBLOX deviation END
end
- return result
end
-
local root = ReactTestRenderer.create(
React.createElement(
Suspense,
{ fallback = React.createElement(Text, { text = "Loading..." }) },
- { React.createElement(BadAsyncText, { text = "Hi" }) }
+ React.createElement(BadAsyncText, { text = "Hi" })
),
- {
- unstable_isConcurrent = true,
- }
+ { unstable_isConcurrent = true }
)
-
- jestExpect(Scheduler).toFlushAndYield({ "Suspend! [Hi]", "Loading..." })
-
- resolveThenable("Hi")
- -- This thenable improperly resolves twice. We should not update the
+ expect(Scheduler).toFlushAndYield({ "Suspend! [Hi]", "Loading..." })
+ resolveThenable("Hi") -- This thenable improperly resolves twice. We should not update the
-- cached value.
resolveThenable("Hi muahahaha I am different")
-
+ -- ROBLOX deviation START: use dot notation
+ -- root:update(
root.update(
+ -- ROBLOX deviation END
React.createElement(
Suspense,
{ fallback = React.createElement(Text, { text = "Loading..." }) },
- { React.createElement(BadAsyncText, { text = "Hi" }) }
+ React.createElement(BadAsyncText, { text = "Hi" })
),
- {
- unstable_isConcurrent = true,
- }
+ { unstable_isConcurrent = true }
)
-
- jestExpect(Scheduler).toHaveYielded({})
- jestExpect(Scheduler).toFlushAndYield({ "Hi" })
- jestExpect(root).toMatchRenderedOutput("Hi")
- end)
-
- it("throws if read is called outside render", function()
- jestExpect(function()
- TextResource.read({ "A", 1000 })
- end).toThrow("read and preload may only be called from within a component's render")
- end)
-
- it("throws if preload is called outside render", function()
- jestExpect(function()
- TextResource.preload({ "A", 1000 })
- end).toThrow("read and preload may only be called from within a component's render")
- end)
+ expect(Scheduler).toHaveYielded({})
+ expect(Scheduler).toFlushAndYield({ "Hi" })
+ expect(root).toMatchRenderedOutput("Hi")
+ end
+ )
+ it("throws if read is called outside render", function()
+ expect(function()
+ -- ROBLOX deviation START: use dot notation
+ -- return TextResource:read({ "A", 1000 })
+ TextResource.read({ "A", 1000 })
+ -- ROBLOX deviation END
+ end).toThrow(
+ "read and preload may only be called from within a component's render"
+ )
+ end)
+ it("throws if preload is called outside render", function()
+ expect(function()
+ -- ROBLOX deviation START: use dot notation
+ -- return TextResource:preload({ "A", 1000 })
+ TextResource.preload({ "A", 1000 })
+ -- ROBLOX deviation END
+ end).toThrow(
+ "read and preload may only be called from within a component's render"
+ )
end)
-end
+end)
diff --git a/modules/react-cache/src/__tests__/init.spec.lua b/modules/react-cache/src/__tests__/init.spec.lua
deleted file mode 100644
index 22f66832..00000000
--- a/modules/react-cache/src/__tests__/init.spec.lua
+++ /dev/null
@@ -1,17 +0,0 @@
-return function()
- local Packages = script.Parent.Parent.Parent
- local jestExpect = require(Packages.Dev.JestRoblox).Globals.expect
- local RobloxJest = require(Packages.Dev.RobloxJest)
- local getTestRendererJestMatchers = require(Packages.Dev.JestReact).getJestMatchers
- local getSchedulerJestMatchers = require(Packages.Scheduler).getJestMatchers
-
- beforeAll(function()
- jestExpect.extend(getTestRendererJestMatchers(jestExpect))
- jestExpect.extend(getSchedulerJestMatchers(jestExpect))
-
- jestExpect.extend({
- toErrorDev = RobloxJest.Matchers.toErrorDev,
- toWarnDev = RobloxJest.Matchers.toWarnDev,
- })
- end)
-end
diff --git a/modules/react-cache/src/init.lua b/modules/react-cache/src/init.lua
index 93ae8313..60d74650 100644
--- a/modules/react-cache/src/init.lua
+++ b/modules/react-cache/src/init.lua
@@ -1 +1,19 @@
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.2/packages/react-cache/index.js
+--[[*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ ]]
+-- ROBLOX deviation START: simplify
+-- local Packages --[[ ROBLOX comment: must define Packages module ]]
+-- local LuauPolyfill = require(Packages.LuauPolyfill)
+-- local Object = LuauPolyfill.Object
+-- local exports = {}
+--
+-- Object.assign(exports, require(script.src.ReactCacheOld))
+-- return exports
return require(script.ReactCacheOld)
+-- ROBLOX deviation END
diff --git a/modules/react-debug-tools/.robloxrc b/modules/react-debug-tools/.robloxrc
new file mode 100644
index 00000000..8f779576
--- /dev/null
+++ b/modules/react-debug-tools/.robloxrc
@@ -0,0 +1,8 @@
+{
+ "language": {
+ "mode": "strict"
+ },
+ "lint": {
+ "*": "enabled"
+ }
+}
\ No newline at end of file
diff --git a/modules/react-debug-tools/default.project.json b/modules/react-debug-tools/default.project.json
new file mode 100644
index 00000000..78363235
--- /dev/null
+++ b/modules/react-debug-tools/default.project.json
@@ -0,0 +1,6 @@
+{
+ "name": "ReactDebugTools",
+ "tree": {
+ "$path": "src"
+ }
+}
diff --git a/modules/react-debug-tools/rotriever.toml b/modules/react-debug-tools/rotriever.toml
new file mode 100644
index 00000000..2541efaf
--- /dev/null
+++ b/modules/react-debug-tools/rotriever.toml
@@ -0,0 +1,19 @@
+[package]
+name = "ReactDebugTools"
+version = { workspace = true }
+authors = { workspace = true }
+publish = true
+content_root = "src"
+
+[dependencies]
+LuauPolyfill = { workspace = true }
+Shared = { path = "../shared" }
+ReactGlobals = { path = "../react-globals" }
+ReactReconciler = { path = "../react-reconciler" }
+
+[dev_dependencies]
+JestGlobals = { workspace = true }
+Promise = { workspace = true }
+React = { path = "../react" }
+ReactTestRenderer = { path = "../react-test-renderer" }
+Scheduler = { path = "../scheduler" }
diff --git a/modules/react-debug-tools/src/ReactDebugHooks.lua b/modules/react-debug-tools/src/ReactDebugHooks.lua
new file mode 100644
index 00000000..9d0642a0
--- /dev/null
+++ b/modules/react-debug-tools/src/ReactDebugHooks.lua
@@ -0,0 +1,1248 @@
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.2/packages/react-debug-tools/src/ReactDebugHooks.js
+--[[*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ ]]
+type void = nil --[[ ROBLOX FIXME: adding `void` type alias to make it easier to use Luau `void` equivalent when supported ]]
+local Packages = script.Parent.Parent
+local LuauPolyfill = require(Packages.LuauPolyfill)
+local Array = LuauPolyfill.Array
+-- ROBLOX deviation START: not needed
+-- local Boolean = LuauPolyfill.Boolean
+-- ROBLOX deviation END
+local Error = LuauPolyfill.Error
+local Map = LuauPolyfill.Map
+local Object = LuauPolyfill.Object
+type Array = LuauPolyfill.Array
+type Error = LuauPolyfill.Error
+type Map = LuauPolyfill.Map
+-- ROBLOX deviation START: add additional imports
+type Object = LuauPolyfill.Object
+local String = LuauPolyfill.String
+-- ROBLOX deviation END
+local exports = {}
+-- local sharedReactTypesModule = require(Packages.shared.ReactTypes)
+-- type MutableSource = sharedReactTypesModule.MutableSource
+-- type MutableSourceGetSnapshotFn = sharedReactTypesModule.MutableSourceGetSnapshotFn
+-- type MutableSourceSubscribeFn = sharedReactTypesModule.MutableSourceSubscribeFn
+-- type ReactContext = sharedReactTypesModule.ReactContext
+-- type ReactProviderType = sharedReactTypesModule.ReactProviderType
+local ReactTypes = require(Packages.Shared)
+type MutableSource = ReactTypes.MutableSource
+type MutableSourceGetSnapshotFn = ReactTypes.MutableSourceGetSnapshotFn<
+ Source,
+ Snapshot
+>
+type MutableSourceSubscribeFn = ReactTypes.MutableSourceSubscribeFn<
+ Source,
+ Snapshot
+>
+type ReactContext = ReactTypes.ReactContext
+type ReactProviderType = ReactTypes.ReactProviderType
+
+-- ROBLOX deviation END
+-- ROBLOX deviation START: add import type that is a built-in in flow
+type React_Node = ReactTypes.React_Node
+-- ROBLOX deviation END
+
+-- ROBLOX deviation START: add binding support
+type ReactBinding = ReactTypes.ReactBinding
+type ReactBindingUpdater = ReactTypes.ReactBindingUpdater
+-- ROBLOX deviation END
+-- ROBLOX deviation START: fix import
+-- local reactReconcilerSrcReactInternalTypesModule =
+-- require(Packages["react-reconciler"].src.ReactInternalTypes)
+local reactReconcilerSrcReactInternalTypesModule = require(Packages.ReactReconciler)
+-- ROBLOX deviation END
+type Fiber = reactReconcilerSrcReactInternalTypesModule.Fiber
+type DispatcherType = reactReconcilerSrcReactInternalTypesModule.Dispatcher
+-- ROBLOX deviation START: fix import - import from Shared
+-- local reactReconcilerSrcReactFiberHostConfigModule =
+-- require(Packages["react-reconciler"].src.ReactFiberHostConfig)
+local reactReconcilerSrcReactFiberHostConfigModule = require(Packages.Shared)
+-- ROBLOX deviation END
+type OpaqueIDType = reactReconcilerSrcReactFiberHostConfigModule.OpaqueIDType
+-- ROBLOX deviation START: fix import
+-- local NoMode = require(Packages["react-reconciler"].src.ReactTypeOfMode).NoMode
+local ReconcilerModule = require(Packages.ReactReconciler)({})
+local NoMode = ReconcilerModule.ReactTypeOfMode.NoMode
+-- ROBLOX deviation END
+-- ROBLOX deviation START: add inline ErrorStackParser implementation
+-- local ErrorStackParser = require(Packages["error-stack-parser"]).default
+type StackFrame = {
+ source: string?,
+ functionName: string?,
+}
+local ErrorStackParser = {
+ parse = function(error_: Error): Array
+ if error_.stack == nil then
+ return {}
+ end
+ local filtered = Array.filter(
+ string.split(error_.stack :: string, "\n"),
+ function(line)
+ return string.find(line, "^LoadedCode") ~= nil
+ end
+ )
+ return Array.map(filtered, function(stackTraceLine)
+ -- ROBLOX FIXME Luau: shouldn't need to explicitly provide nilable field
+ local functionName = string.match(stackTraceLine, "function (%w+)$")
+ return { source = stackTraceLine, functionName = functionName }
+ end)
+ end,
+}
+-- ROBLOX deviation END
+-- ROBLOX deviation START: import from Shared
+-- local ReactSharedInternals = require(Packages.shared.ReactSharedInternals).default
+-- local REACT_OPAQUE_ID_TYPE = require(Packages.shared.ReactSymbols).REACT_OPAQUE_ID_TYPE
+local SharedModule = require(Packages.Shared)
+local ReactSharedInternals = SharedModule.ReactSharedInternals
+local ReactSymbols = SharedModule.ReactSymbols
+local REACT_OPAQUE_ID_TYPE = ReactSymbols.REACT_OPAQUE_ID_TYPE
+-- ROBLOX deviation END
+-- ROBLOX deviation START: fix import - get from ReconcilerModule
+-- local reactReconcilerSrcReactWorkTagsModule =
+-- require(Packages["react-reconciler"].src.ReactWorkTags)
+local reactReconcilerSrcReactWorkTagsModule = ReconcilerModule.ReactWorkTags
+-- ROBLOX deviation END
+local FunctionComponent = reactReconcilerSrcReactWorkTagsModule.FunctionComponent
+local SimpleMemoComponent = reactReconcilerSrcReactWorkTagsModule.SimpleMemoComponent
+local ContextProvider = reactReconcilerSrcReactWorkTagsModule.ContextProvider
+local ForwardRef = reactReconcilerSrcReactWorkTagsModule.ForwardRef
+local Block = reactReconcilerSrcReactWorkTagsModule.Block
+-- ROBLOX deviation START: fix import
+-- type CurrentDispatcherRef = typeof(ReactSharedInternals_ReactCurrentDispatcher) -- Used to track hooks called during a render
+type CurrentDispatcherRef = typeof(ReactSharedInternals.ReactCurrentDispatcher)
+-- ROBLOX deviation END
+type HookLogEntry = { primitive: string, stackError: Error, value: unknown } --[[ ROBLOX CHECK: inexact type upstream which is not supported by Luau. Verify if it doesn't break the analyze ]]
+local hookLog: Array = {} -- Primitives
+type BasicStateAction = (S) -> S | S
+type Dispatch = (A) -> ()
+local primitiveStackCache: nil --[[ ROBLOX CHECK: verify if `null` wasn't used differently than `undefined` ]] | Map> =
+ nil
+local currentFiber: Fiber | nil --[[ ROBLOX CHECK: verify if `null` wasn't used differently than `undefined` ]] =
+ nil
+type Hook = {
+ memoizedState: any,
+ next: Hook | nil,--[[ ROBLOX CHECK: verify if `null` wasn't used differently than `undefined` ]]
+}
+-- ROBLOX deviation START: add predefined variable
+local Dispatcher: DispatcherType
+-- ROBLOX deviation END
+local function getPrimitiveStackCache(): Map>
+ -- This initializes a cache of all primitive hooks so that the top
+ -- most stack frames added by calling the primitive hook can be removed.
+ if primitiveStackCache == nil then
+ local cache = Map.new()
+ local readHookLog
+ do --[[ ROBLOX COMMENT: try-finally block conversion ]]
+ -- ROBLOX deviation START: doesn't return
+ -- local ok, result, hasReturned = pcall(function()
+ local ok, result = pcall(function()
+ -- ROBLOX deviation END
+ -- Use all hooks here to add them to the hook log.
+ -- ROBLOX deviation START: use dot notation
+ -- Dispatcher:useContext({ _currentValue = nil } :: any)
+ -- Dispatcher:useState(nil)
+ -- Dispatcher:useReducer(function(s, a)
+ Dispatcher.useContext({ _currentValue = nil } :: any)
+ Dispatcher.useState(nil)
+ Dispatcher.useReducer(function(s, a)
+ -- ROBLOX deviation END
+ return s
+ end, nil)
+ -- ROBLOX deviation START: use dot notation
+ -- Dispatcher:useRef(nil)
+ -- Dispatcher:useLayoutEffect(function() end)
+ -- Dispatcher:useEffect(function() end)
+ -- Dispatcher:useImperativeHandle(nil, function()
+ Dispatcher.useRef(nil)
+ Dispatcher.useLayoutEffect(function() end)
+ Dispatcher.useEffect(function() end)
+ Dispatcher.useImperativeHandle(nil, function()
+ -- ROBLOX deviation END
+ return nil
+ end)
+ -- ROBLOX deviation START: use dot notation
+ -- Dispatcher:useDebugValue(nil)
+ -- Dispatcher:useCallback(function() end)
+ -- Dispatcher:useMemo(function()
+ Dispatcher.useDebugValue(nil)
+ Dispatcher.useCallback(function() end)
+ Dispatcher.useMemo(function()
+ -- ROBLOX deviation END
+ return nil
+ end)
+ end)
+ do
+ readHookLog = hookLog
+ hookLog = {}
+ end
+ -- ROBLOX deviation START: doesn't return
+ -- if hasReturned then
+ -- return result
+ -- end
+ -- ROBLOX deviation END
+ if not ok then
+ error(result)
+ end
+ end
+ -- ROBLOX deviation START: use for in loop instead of while
+ -- do
+ -- local i = 0
+ -- while
+ -- i
+ -- < readHookLog.length --[[ ROBLOX CHECK: operator '<' works only if either both arguments are strings or both are a number ]]
+ -- do
+ -- local hook = readHookLog[tostring(i)]
+ -- cache:set(hook.primitive, ErrorStackParser:parse(hook.stackError))
+ -- i += 1
+ -- end
+ -- end
+ for i = 1, #readHookLog do
+ local hook = readHookLog[i]
+ cache:set(hook.primitive, ErrorStackParser.parse(hook.stackError))
+ end
+ -- ROBLOX deviation END
+ primitiveStackCache = cache
+ end
+ -- ROBLOX deviation START: needs cast
+ -- return primitiveStackCache
+ return primitiveStackCache :: Map>
+ -- ROBLOX deviation END
+end
+local currentHook: nil --[[ ROBLOX CHECK: verify if `null` wasn't used differently than `undefined` ]] | Hook =
+ nil
+local function nextHook(
+): nil --[[ ROBLOX CHECK: verify if `null` wasn't used differently than `undefined` ]] | Hook
+ local hook = currentHook
+ if hook ~= nil then
+ currentHook = hook.next
+ end
+ return hook
+end
+local function readContext(
+ context: ReactContext,
+ observedBits: void | number | boolean
+): T
+ -- For now we don't expose readContext usage in the hooks debugging info.
+ return context._currentValue
+end
+local function useContext(
+ context: ReactContext,
+ observedBits: void | number | boolean
+): T
+ table.insert(
+ hookLog,
+ { primitive = "Context", stackError = Error.new(), value = context._currentValue }
+ ) --[[ ROBLOX CHECK: check if 'hookLog' is an Array ]]
+ return context._currentValue
+end
+-- ROBLOX deviation START: return 2 values instead of a tuple
+-- local function useState(
+-- initialState: () -> S | S
+-- ): any --[[ ROBLOX TODO: Unhandled node for type: TupleTypeAnnotation ]] --[[ [S, Dispatch>] ]]
+local function useState(initialState: (() -> S) | S): (S, Dispatch>)
+ -- ROBLOX deviation END
+ local hook = nextHook()
+ local state: S = if hook ~= nil
+ then hook.memoizedState
+ else if typeof(initialState) == "function"
+ then -- $FlowFixMe: Flow doesn't like mixed types
+ initialState()
+ else initialState
+ table.insert(
+ hookLog,
+ { primitive = "State", stackError = Error.new(), value = state }
+ ) --[[ ROBLOX CHECK: check if 'hookLog' is an Array ]]
+ -- ROBLOX deviation START: return 2 values instead of a tuple
+ -- return { state, function(action: BasicStateAction) end }
+ return state, function(action: BasicStateAction) end
+ -- ROBLOX deviation END
+end
+-- ROBLOX deviation START: return 2 values instead of a tuple
+-- local function useReducer(
+-- reducer: (S, A) -> S,
+-- initialArg: I,
+-- init: ((I) -> S)?
+-- ): any --[[ ROBLOX TODO: Unhandled node for type: TupleTypeAnnotation ]] --[[ [S, Dispatch] ]]
+local function useReducer(
+ reducer: (S, A) -> S,
+ initialArg: I,
+ init: ((I) -> S)?
+): (S, Dispatch)
+ -- ROBLOX deviation END
+ local hook = nextHook()
+ local state
+ if hook ~= nil then
+ state = hook.memoizedState
+ else
+ state = if init ~= nil then init(initialArg) else (initialArg :: any) :: S
+ end
+ table.insert(
+ hookLog,
+ { primitive = "Reducer", stackError = Error.new(), value = state }
+ ) --[[ ROBLOX CHECK: check if 'hookLog' is an Array ]]
+ -- ROBLOX deviation START: return 2 values instead of a tuple
+ -- return { state, function(action: A) end }
+ return state, function(action: A) end
+ -- ROBLOX deviation END
+end
+local function useRef(initialValue: T): { current: T }
+ local hook = nextHook()
+ local ref = if hook ~= nil then hook.memoizedState else { current = initialValue }
+ table.insert(
+ hookLog,
+ { primitive = "Ref", stackError = Error.new(), value = ref.current }
+ ) --[[ ROBLOX CHECK: check if 'hookLog' is an Array ]]
+ return ref
+end
+-- ROBLOX deviation START: add binding support; these aren't fully working hooks, so this
+-- is just an approximation modeled off of the `ref` hook above
+local function useBinding(initialValue: T): (ReactBinding, ReactBindingUpdater)
+ local hook = nextHook()
+ local binding = if hook ~= nil
+ then hook.memoizedState
+ else ({
+ getValue = function(_self)
+ return initialValue
+ end,
+ } :: any) :: ReactBinding
+
+ table.insert(hookLog, {
+ primitive = "Binding",
+ stackError = Error.new(),
+ value = binding:getValue(),
+ })
+
+ return binding, function(_value) end
+end
+-- ROBLOX deviation END
+local function useLayoutEffect(
+ -- ROBLOX deviation START: Luau needs union type packs for this type to translate idiomatically
+ -- create: () -> () -> () | void,
+ create: (() -> ()) | (() -> () -> ()),
+ -- ROBLOX deviation END
+ inputs: Array | void | nil --[[ ROBLOX CHECK: verify if `null` wasn't used differently than `undefined` ]]
+): ()
+ nextHook()
+ table.insert(
+ hookLog,
+ { primitive = "LayoutEffect", stackError = Error.new(), value = create }
+ ) --[[ ROBLOX CHECK: check if 'hookLog' is an Array ]]
+end
+local function useEffect(
+ -- ROBLOX deviation START: Luau needs union type packs for this type to translate idiomatically
+ -- create: () -> () -> () | void,
+ create: (() -> ()) | (() -> () -> ()),
+ -- ROBLOX deviation END
+ inputs: Array | void | nil --[[ ROBLOX CHECK: verify if `null` wasn't used differently than `undefined` ]]
+): ()
+ nextHook()
+ table.insert(
+ hookLog,
+ { primitive = "Effect", stackError = Error.new(), value = create }
+ ) --[[ ROBLOX CHECK: check if 'hookLog' is an Array ]]
+end
+local function useImperativeHandle(
+ ref: {
+ current: T | nil,--[[ ROBLOX CHECK: verify if `null` wasn't used differently than `undefined` ]]
+ } | (
+ inst: T | nil --[[ ROBLOX CHECK: verify if `null` wasn't used differently than `undefined` ]]
+ ) -> unknown | nil --[[ ROBLOX CHECK: verify if `null` wasn't used differently than `undefined` ]] | void,
+ create: () -> T,
+ inputs: Array | void | nil --[[ ROBLOX CHECK: verify if `null` wasn't used differently than `undefined` ]]
+): ()
+ nextHook() -- We don't actually store the instance anywhere if there is no ref callback
+ -- and if there is a ref callback it might not store it but if it does we
+ -- have no way of knowing where. So let's only enable introspection of the
+ -- ref itself if it is using the object form.
+ local instance = nil
+ if ref ~= nil and typeof(ref) == "table" then
+ instance = ref.current
+ end
+ table.insert(
+ hookLog,
+ { primitive = "ImperativeHandle", stackError = Error.new(), value = instance }
+ ) --[[ ROBLOX CHECK: check if 'hookLog' is an Array ]]
+end
+-- ROBLOX deviation START: add generic params
+-- local function useDebugValue(value: any, formatterFn: ((value: any) -> any)?)
+local function useDebugValue(value: T, formatterFn: ((value: T) -> any)?): ()
+ -- ROBLOX deviation END
+ table.insert(hookLog, {
+ primitive = "DebugValue",
+ stackError = Error.new(),
+ value = if typeof(formatterFn) == "function" then formatterFn(value) else value,
+ }) --[[ ROBLOX CHECK: check if 'hookLog' is an Array ]]
+end
+local function useCallback(
+ callback: T,
+ inputs: Array | void | nil --[[ ROBLOX CHECK: verify if `null` wasn't used differently than `undefined` ]]
+): T
+ local hook = nextHook()
+ table.insert(hookLog, {
+ primitive = "Callback",
+ stackError = Error.new(),
+ value = if hook ~= nil
+ then hook.memoizedState[
+ 1 --[[ ROBLOX adaptation: added 1 to array index ]]
+ ]
+ else callback,
+ }) --[[ ROBLOX CHECK: check if 'hookLog' is an Array ]]
+ return callback
+end
+-- ROBLOX deviation START: FIXME Luau: work around 'Failed to unify type packs' error: CLI-51338
+-- local function useMemo(
+-- nextCreate: () -> T,
+-- inputs: Array | void | nil --[[ ROBLOX CHECK: verify if `null` wasn't used differently than `undefined` ]]
+-- ): T
+local function useMemo(nextCreate: () -> T..., inputs: Array | nil): ...any
+ -- ROBLOX deviation END
+ local hook = nextHook()
+ -- ROBLOX deviation START: Wrap memoized values in a table and unpack to allow for multiple return values
+ -- local value = if hook ~= nil
+ -- then hook.memoizedState[
+ -- 1 --[[ ROBLOX adaptation: added 1 to array index ]]
+ -- ]
+ -- else nextCreate()
+ local value = if hook ~= nil then hook.memoizedState[1] else { nextCreate() }
+ -- ROBLOX deviation END
+
+ table.insert(hookLog, { primitive = "Memo", stackError = Error.new(), value = value }) --[[ ROBLOX CHECK: check if 'hookLog' is an Array ]]
+ -- ROBLOX deviation START: unwrap memoized values in a table
+ -- return value
+ return table.unpack(value)
+ -- ROBLOX deviation END
+end
+local function useMutableSource(
+ source: MutableSource,
+ getSnapshot: MutableSourceGetSnapshotFn<
+ Source,
+ Snapshot
+ >,
+ subscribe: MutableSourceSubscribeFn<
+ Source,
+ Snapshot
+ >
+): Snapshot
+ -- useMutableSource() composes multiple hooks internally.
+ -- Advance the current hook index the same number of times
+ -- so that subsequent hooks have the right memoized state.
+ nextHook() -- MutableSource
+ nextHook() -- State
+ nextHook() -- Effect
+ nextHook() -- Effect
+ local value = getSnapshot(source._source)
+ table.insert(
+ hookLog,
+ { primitive = "MutableSource", stackError = Error.new(), value = value }
+ ) --[[ ROBLOX CHECK: check if 'hookLog' is an Array ]]
+ return value
+end
+-- ROBLOX deviation START: enable these once they are fully enabled in the Dispatcher type and in ReactFiberHooks' myriad dispatchers
+-- local function useTransition(
+-- ): any --[[ ROBLOX TODO: Unhandled node for type: TupleTypeAnnotation ]] --[[ [(() => void) => void, boolean] ]]
+-- -- useTransition() composes multiple hooks internally.
+-- -- Advance the current hook index the same number of times
+-- -- so that subsequent hooks have the right memoized state.
+-- nextHook() -- State
+-- nextHook() -- Callback
+-- table.insert(
+-- hookLog,
+-- { primitive = "Transition", stackError = Error.new(), value = nil }
+-- ) --[[ ROBLOX CHECK: check if 'hookLog' is an Array ]]
+-- return { function(callback) end, false }
+-- end
+-- local function useDeferredValue(value: T): T
+-- -- useDeferredValue() composes multiple hooks internally.
+-- -- Advance the current hook index the same number of times
+-- -- so that subsequent hooks have the right memoized state.
+-- nextHook() -- State
+-- nextHook() -- Effect
+-- table.insert(
+-- hookLog,
+-- { primitive = "DeferredValue", stackError = Error.new(), value = value }
+-- ) --[[ ROBLOX CHECK: check if 'hookLog' is an Array ]]
+-- return value
+-- end
+-- ROBLOX deviation END
+local function useOpaqueIdentifier(): OpaqueIDType | void
+ local hook = nextHook() -- State
+ -- ROBLOX deviation START: simplify
+ -- if
+ -- Boolean.toJSBoolean(
+ -- if Boolean.toJSBoolean(currentFiber)
+ -- then currentFiber.mode == NoMode
+ -- else currentFiber
+ -- )
+ -- then
+ if currentFiber and currentFiber.mode == NoMode then
+ -- ROBLOX deviation END
+ nextHook() -- Effect
+ end
+ local value = if hook == nil then nil else hook.memoizedState
+ -- ROBLOX deviation START: simplify
+ -- if
+ -- Boolean.toJSBoolean(
+ -- if Boolean.toJSBoolean(value)
+ -- then value["$$typeof"] == REACT_OPAQUE_ID_TYPE
+ -- else value
+ -- )
+ -- then
+ if value and (value :: any)["$$typeof"] == REACT_OPAQUE_ID_TYPE then
+ -- ROBLOX deviation END
+ value = nil
+ end
+ table.insert(
+ hookLog,
+ { primitive = "OpaqueIdentifier", stackError = Error.new(), value = value }
+ ) --[[ ROBLOX CHECK: check if 'hookLog' is an Array ]]
+ return value
+end
+-- ROBLOX deviation START: predefined variable
+-- local Dispatcher: DispatcherType = {
+Dispatcher = {
+ -- ROBLOX deviation END
+ readContext = readContext,
+ useCallback = useCallback,
+ useContext = useContext,
+ useEffect = useEffect,
+ -- ROBLOX deviation START: needs cast
+ -- useImperativeHandle = useImperativeHandle,
+ useImperativeHandle = useImperativeHandle :: any,
+ -- ROBLOX deviation END
+ useDebugValue = useDebugValue,
+ useLayoutEffect = useLayoutEffect,
+ -- ROBLOX deviation START: needs cast
+ -- useMemo = useMemo,
+ useMemo = useMemo :: any,
+ -- ROBLOX deviation END
+ useReducer = useReducer,
+ useRef = useRef,
+ -- ROBLOX deviation START: add useBinding
+ useBinding = useBinding,
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: needs cast
+ -- useState = useState,
+ useState = useState :: any,
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: not implemented
+ -- useTransition = useTransition,
+ -- ROBLOX deviation END
+ useMutableSource = useMutableSource,
+ -- ROBLOX deviation START: not implemented
+ -- useDeferredValue = useDeferredValue,
+ -- ROBLOX deviation END
+ useOpaqueIdentifier = useOpaqueIdentifier,
+} -- Inspect
+export type HooksNode = {
+ id: number | nil,--[[ ROBLOX CHECK: verify if `null` wasn't used differently than `undefined` ]]
+ isStateEditable: boolean,
+ name: string,
+ value: unknown,
+ subHooks: Array,
+} --[[ ROBLOX CHECK: inexact type upstream which is not supported by Luau. Verify if it doesn't break the analyze ]]
+export type HooksTree = Array -- Don't assume
+--
+-- We can't assume that stack frames are nth steps away from anything.
+-- E.g. we can't assume that the root call shares all frames with the stack
+-- of a hook call. A simple way to demonstrate this is wrapping `new Error()`
+-- in a wrapper constructor like a polyfill. That'll add an extra frame.
+-- Similar things can happen with the call to the dispatcher. The top frame
+-- may not be the primitive. Likewise the primitive can have fewer stack frames
+-- such as when a call to useState got inlined to use dispatcher.useState.
+--
+-- We also can't assume that the last frame of the root call is the same
+-- frame as the last frame of the hook call because long stack traces can be
+-- truncated to a stack trace limit.
+-- ROBLOX deviation START: adapt to 1-based indexing
+-- local mostLikelyAncestorIndex = 0
+local mostLikelyAncestorIndex = 1
+-- ROBLOX deviation END
+-- ROBLOX deviation START: explicit type
+-- local function findSharedIndex(hookStack, rootStack, rootIndex)
+local function findSharedIndex(hookStack, rootStack, rootIndex: number)
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: don't use tostring
+ -- local source = rootStack[tostring(rootIndex)].source
+ local source = rootStack[rootIndex].source
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: implement LabeledStatement
+ -- error("not implemented") --[[ ROBLOX TODO: Unhandled node for type: LabeledStatement ]] --[[ hookSearch: for (let i = 0; i < hookStack.length; i++) {
+ -- if (hookStack[i].source === source) {
+ -- // This looks like a match. Validate that the rest of both stack match up.
+ -- for (let a = rootIndex + 1, b = i + 1; a < rootStack.length && b < hookStack.length; a++, b++) {
+ -- if (hookStack[b].source !== rootStack[a].source) {
+ -- // If not, give up and try a different match.
+ -- continue hookSearch;
+ -- }
+ -- }
+
+ -- return i;
+ -- }
+ -- } ]]
+ for i = 1, #hookStack do
+ if hookStack[i].source == source then
+ -- This looks like a match. Validate that the rest of both stack match up.
+ -- ROBLOX deviation: rewrite complex loop
+ local a = rootIndex + 1
+ local b = i + 1
+ local skipReturn = false
+ while a <= #rootStack and b <= #hookStack do
+ if hookStack[b].source ~= rootStack[a].source then
+ -- If not, give up and try a different match.
+ skipReturn = true
+ break
+ end
+ a += 1
+ b += 1
+ end
+ if not skipReturn then
+ return i
+ end
+ end
+ end
+ -- ROBLOX deviation END
+ return -1
+end
+local function findCommonAncestorIndex(rootStack, hookStack)
+ local rootIndex = findSharedIndex(hookStack, rootStack, mostLikelyAncestorIndex)
+ if rootIndex ~= -1 then
+ return rootIndex
+ end -- If the most likely one wasn't a hit, try any other frame to see if it is shared.
+ -- If that takes more than 5 frames, something probably went wrong.
+ -- ROBLOX deviation START: use numeric for loop
+ -- do
+ -- local i = 0
+ -- while
+ -- i < rootStack.length --[[ ROBLOX CHECK: operator '<' works only if either both arguments are strings or both are a number ]]
+ -- and i < 5 --[[ ROBLOX CHECK: operator '<' works only if either both arguments are strings or both are a number ]]
+ -- do
+ -- rootIndex = findSharedIndex(hookStack, rootStack, i)
+ -- if rootIndex ~= -1 then
+ -- mostLikelyAncestorIndex = i
+ -- return rootIndex
+ -- end
+ -- i += 1
+ -- end
+ -- end
+ for i = 1, math.min(#rootStack, 5) do
+ rootIndex = findSharedIndex(hookStack, rootStack, i)
+ if rootIndex ~= -1 then
+ mostLikelyAncestorIndex = i
+ return rootIndex
+ end
+ end
+ -- ROBLOX deviation END
+ return -1
+end
+local function isReactWrapper(functionName, primitiveName)
+ -- ROBLOX deviation START: simplify
+ -- if not Boolean.toJSBoolean(functionName) then
+ if not functionName or functionName == "" then
+ -- ROBLOX deviation END
+ return false
+ end
+ local expectedPrimitiveName = "use" .. tostring(primitiveName)
+ -- ROBLOX deviation START: fix length implementation + Luau doesn't understand the guard above
+ -- if
+ -- functionName.length
+ -- < expectedPrimitiveName.length --[[ ROBLOX CHECK: operator '<' works only if either both arguments are strings or both are a number ]]
+ -- then
+ if string.len(functionName :: string) < string.len(expectedPrimitiveName) then
+ -- ROBLOX deviation END
+ return false
+ end
+ -- ROBLOX deviation START: fix length implementation + Luau doesn't understand the guard above
+ -- return functionName:lastIndexOf(expectedPrimitiveName)
+ -- == functionName.length - expectedPrimitiveName.length
+ return String.lastIndexOf(functionName :: string, expectedPrimitiveName)
+ == (string.len(functionName :: string) - string.len(expectedPrimitiveName) + 1)
+ -- ROBLOX deviation END
+end
+local function findPrimitiveIndex(hookStack, hook)
+ local stackCache = getPrimitiveStackCache()
+ local primitiveStack = stackCache:get(hook.primitive)
+ if primitiveStack == nil then
+ return -1
+ end
+ -- ROBLOX deviation START: use numeric for loop and precompute iteration count
+ -- do
+ -- local i = 0
+ -- while
+ -- i < primitiveStack.length --[[ ROBLOX CHECK: operator '<' works only if either both arguments are strings or both are a number ]]
+ -- and i < hookStack.length --[[ ROBLOX CHECK: operator '<' works only if either both arguments are strings or both are a number ]]
+ -- do
+ -- if primitiveStack[tostring(i)].source ~= hookStack[tostring(i)].source then
+ -- -- If the next two frames are functions called `useX` then we assume that they're part of the
+ -- -- wrappers that the React packager or other packages adds around the dispatcher.
+ -- if
+ -- Boolean.toJSBoolean(
+ -- i < hookStack.length - 1 --[[ ROBLOX CHECK: operator '<' works only if either both arguments are strings or both are a number ]]
+ -- and isReactWrapper(
+ -- hookStack[tostring(i)].functionName,
+ -- hook.primitive
+ -- )
+ -- )
+ -- then
+ -- i += 1
+ -- end
+ -- if
+ -- Boolean.toJSBoolean(
+ -- i < hookStack.length - 1 --[[ ROBLOX CHECK: operator '<' works only if either both arguments are strings or both are a number ]]
+ -- and isReactWrapper(
+ -- hookStack[tostring(i)].functionName,
+ -- hook.primitive
+ -- )
+ -- )
+ -- then
+ -- i += 1
+ -- end
+ -- return i
+ -- end
+ -- i += 1
+ -- end
+ -- end
+ for i = 1, math.min(#primitiveStack :: Array, #hookStack) do
+ if (primitiveStack :: Array)[i].source ~= hookStack[i].source then
+ -- If the next two frames are functions called `useX` then we assume that they're part of the
+ -- wrappers that the React packager or other packages adds around the dispatcher.
+ -- ROBLOX NOTE: 1-indexed so drop -1
+ if
+ i < #hookStack
+ and isReactWrapper(hookStack[i].functionName, hook.primitive)
+ then
+ i += 1
+ end
+ -- ROBLOX NOTE: 1-indexed so drop -1
+ if
+ i < #hookStack
+ and isReactWrapper(hookStack[i].functionName, hook.primitive)
+ then
+ i += 1
+ end
+ return i
+ end
+ end
+ -- ROBLOX deviation END
+ return -1
+end
+-- ROBLOX deviation START: Luau doesn't infer Array | nil like it should
+-- local function parseTrimmedStack(rootStack, hook)
+local function parseTrimmedStack(rootStack, hook): Array?
+ -- ROBLOX deviation END
+ -- Get the stack trace between the primitive hook function and
+ -- the root function call. I.e. the stack frames of custom hooks.
+ -- ROBLOX deviation START: use dot notation
+ -- local hookStack = ErrorStackParser:parse(hook.stackError)
+ local hookStack = ErrorStackParser.parse(hook.stackError)
+ -- ROBLOX deviation END
+ local rootIndex = findCommonAncestorIndex(rootStack, hookStack)
+ local primitiveIndex = findPrimitiveIndex(hookStack, hook)
+ if
+ rootIndex == -1
+ or primitiveIndex == -1
+ or rootIndex - primitiveIndex < 2 --[[ ROBLOX CHECK: operator '<' works only if either both arguments are strings or both are a number ]]
+ then
+ -- Something went wrong. Give up.
+ return nil
+ end
+ return Array.slice(hookStack, primitiveIndex, rootIndex - 1) --[[ ROBLOX CHECK: check if 'hookStack' is an Array ]]
+end
+local function parseCustomHookName(functionName: void | string): string
+ -- ROBLOX deviation START: simplify
+ -- if not Boolean.toJSBoolean(functionName) then
+ if not functionName then
+ -- ROBLOX deviation END
+ return ""
+ end
+ -- ROBLOX deviation START: fix implementation
+ -- local startIndex = functionName:lastIndexOf(".")
+ local startIndex = String.lastIndexOf(functionName :: string, ".")
+ -- ROBLOX deviation END
+ if startIndex == -1 then
+ -- ROBLOX deviation START: adapt for 1-based indexing
+ -- startIndex = 0
+ startIndex = 1
+ -- ROBLOX deviation END
+ end
+ -- ROBLOX deviation START: fix implementation
+ -- if functionName:substr(startIndex, 3) == "use" then
+ if String.substr(functionName :: string, startIndex, 3) == "use" then
+ -- ROBLOX deviation END
+ startIndex += 3
+ end
+ -- ROBLOX deviation START: fix implementation
+ -- return functionName:substr(startIndex)
+ return String.substr(functionName :: string, startIndex)
+ -- ROBLOX deviation END
+end
+-- ROBLOX deviation START: add predefined function
+local processDebugValues
+-- ROBLOX deviation END
+-- ROBLOX deviation START: explicit type
+-- local function buildTree(rootStack, readHookLog): HooksTree
+local function buildTree(rootStack, readHookLog: Array): HooksTree
+ -- ROBLOX deviation END
+ local rootChildren = {}
+ local prevStack = nil
+ local levelChildren = rootChildren
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- local nativeHookID = 0
+ local nativeHookID = 1
+ -- ROBLOX deviation END
+ local stackOfChildren = {}
+ -- ROBLOX deviation START: use numeric for loop
+ -- do
+ -- local i = 0
+ -- while
+ -- i
+ -- < readHookLog.length --[[ ROBLOX CHECK: operator '<' works only if either both arguments are strings or both are a number ]]
+ -- do
+ -- local hook = readHookLog[tostring(i)]
+ -- local stack = parseTrimmedStack(rootStack, hook)
+ -- if stack ~= nil then
+ -- -- Note: The indices 0 <= n < length-1 will contain the names.
+ -- -- The indices 1 <= n < length will contain the source locations.
+ -- -- That's why we get the name from n - 1 and don't check the source
+ -- -- of index 0.
+ -- local commonSteps = 0
+ -- if prevStack ~= nil then
+ -- -- Compare the current level's stack to the new stack.
+ -- while
+ -- commonSteps < stack.length --[[ ROBLOX CHECK: operator '<' works only if either both arguments are strings or both are a number ]]
+ -- and commonSteps < prevStack.length --[[ ROBLOX CHECK: operator '<' works only if either both arguments are strings or both are a number ]]
+ -- do
+ -- local stackSource =
+ -- stack[tostring(stack.length - commonSteps - 1)].source
+ -- local prevSource =
+ -- prevStack[tostring(prevStack.length - commonSteps - 1)].source
+ -- if stackSource ~= prevSource then
+ -- break
+ -- end
+ -- commonSteps += 1
+ -- end -- Pop back the stack as many steps as were not common.
+ -- do
+ -- local j = prevStack.length - 1
+ -- while
+ -- j
+ -- > commonSteps --[[ ROBLOX CHECK: operator '>' works only if either both arguments are strings or both are a number ]]
+ -- do
+ -- levelChildren = table.remove(stackOfChildren) --[[ ROBLOX CHECK: check if 'stackOfChildren' is an Array ]]
+ -- j -= 1
+ -- end
+ -- end
+ -- end -- The remaining part of the new stack are custom hooks. Push them
+ -- -- to the tree.
+ -- do
+ -- local j = stack.length - commonSteps - 1
+ -- while
+ -- j
+ -- >= 1 --[[ ROBLOX CHECK: operator '>=' works only if either both arguments are strings or both are a number ]]
+ -- do
+ -- local children = {}
+ -- table.insert(levelChildren, {
+ -- id = nil,
+ -- isStateEditable = false,
+ -- name = parseCustomHookName(
+ -- stack[tostring(j - 1)].functionName
+ -- ),
+ -- value = nil,
+ -- subHooks = children,
+ -- }) --[[ ROBLOX CHECK: check if 'levelChildren' is an Array ]]
+ -- table.insert(stackOfChildren, levelChildren) --[[ ROBLOX CHECK: check if 'stackOfChildren' is an Array ]]
+ -- levelChildren = children
+ -- j -= 1
+ -- end
+ -- end
+ -- prevStack = stack
+ -- end
+ -- local primitive = hook.primitive -- For now, the "id" of stateful hooks is just the stateful hook index.
+ -- -- Custom hooks have no ids, nor do non-stateful native hooks (e.g. Context, DebugValue).
+ -- local id = if primitive == "Context" or primitive == "DebugValue"
+ -- then nil
+ -- else (function()
+ -- local ref = nativeHookID
+ -- nativeHookID += 1
+ -- return ref
+ -- end)() -- For the time being, only State and Reducer hooks support runtime overrides.
+ -- local isStateEditable = primitive == "Reducer" or primitive == "State"
+ -- table.insert(levelChildren, {
+ -- id = id,
+ -- isStateEditable = isStateEditable,
+ -- name = primitive,
+ -- value = hook.value,
+ -- subHooks = {},
+ -- }) --[[ ROBLOX CHECK: check if 'levelChildren' is an Array ]]
+ -- i += 1
+ -- end
+ -- end -- Associate custom hook values (useDebugValue() hook entries) with the correct hooks.
+ for i = 1, #readHookLog do
+ local hook = readHookLog[i]
+ local stack = parseTrimmedStack(rootStack, hook)
+
+ if stack ~= nil then
+ -- Note: The indices 0 <= n < length-1 will contain the names.
+ -- The indices 1 <= n < length will contain the source locations.
+ -- That's why we get the name from n - 1 and don't check the source
+ -- of index 0.
+ local commonSteps = 0
+ if prevStack ~= nil then
+ -- Compare the current level's stack to the new stack.
+ while commonSteps < #stack and commonSteps < #prevStack do
+ local stackSource = stack[#stack - commonSteps].source
+ local prevSource = prevStack[#prevStack - commonSteps].source
+
+ if stackSource ~= prevSource then
+ break
+ end
+
+ commonSteps += 1
+ end
+ -- Pop back the stack as many steps as were not common.
+ for j = #prevStack - 1, commonSteps + 1, -1 do
+ levelChildren =
+ table.remove(stackOfChildren :: Array) :: Array
+ end
+ end
+
+ -- The remaining part of the new stack are custom hooks. Push them
+ -- to the tree.
+ for j = #stack - commonSteps, 2, -1 do
+ local children = {}
+ table.insert(levelChildren, {
+ -- ROBLOX FIXME Luau: Luau should infer number | nil here by (at least) looking at the function-level usage
+ id = nil :: number | nil,
+ isStateEditable = false,
+ name = parseCustomHookName(stack[j - 1].functionName),
+ value = nil,
+ subHooks = children,
+ })
+ table.insert(stackOfChildren, levelChildren)
+ levelChildren = children
+ end
+
+ prevStack = stack
+ end
+
+ local function POSTFIX_INCREMENT()
+ local prev = nativeHookID
+ nativeHookID += 1
+ return prev
+ end
+
+ local primitive = hook.primitive
+
+ -- For now, the "id" of stateful hooks is just the stateful hook index.
+ -- Custom hooks have no ids, nor do non-stateful native hooks (e.g. Context, DebugValue).
+ -- ROBLOX FIXME Luau: Luau doesn't infer number | nil like it should
+ local id = if primitive == "Context" or primitive == "DebugValue"
+ then nil
+ else POSTFIX_INCREMENT()
+ -- For the time being, only State and Reducer hooks support runtime overrides.
+ local isStateEditable = primitive == "Reducer" or primitive == "State"
+
+ table.insert(levelChildren, {
+ id = id,
+ isStateEditable = isStateEditable,
+ name = primitive,
+ value = hook.value,
+ subHooks = {},
+ })
+ end
+ -- ROBLOX deviation END
+ processDebugValues(rootChildren, nil)
+ return rootChildren
+end -- Custom hooks support user-configurable labels (via the special useDebugValue() hook).
+-- That hook adds user-provided values to the hooks tree,
+-- but these values aren't intended to appear alongside of the other hooks.
+-- Instead they should be attributed to their parent custom hook.
+-- This method walks the tree and assigns debug values to their custom hook owners.
+-- ROBLOX deviation START: predefined function
+-- local function processDebugValues(
+function processDebugValues(
+ -- ROBLOX deviation END
+ hooksTree: HooksTree,
+ parentHooksNode: HooksNode | nil --[[ ROBLOX CHECK: verify if `null` wasn't used differently than `undefined` ]]
+): ()
+ local debugValueHooksNodes: Array = {}
+ do
+ -- ROBLOX deviation START: adapt for 1-based indexing
+ -- local i = 0
+ local i = 1
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: fix length implementation
+ -- while
+ -- i
+ -- < hooksTree.length --[[ ROBLOX CHECK: operator '<' works only if either both arguments are strings or both are a number ]]
+ -- do
+ while i <= #hooksTree do
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: don't use tostring for iterating an array
+ -- local hooksNode = hooksTree[tostring(i)]
+ local hooksNode = hooksTree[i]
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: fix length implementation
+ -- if hooksNode.name == "DebugValue" and hooksNode.subHooks.length == 0 then
+ if hooksNode.name == "DebugValue" and #hooksNode.subHooks == 0 then
+ -- ROBLOX deviation END
+ Array.splice(hooksTree, i, 1) --[[ ROBLOX CHECK: check if 'hooksTree' is an Array ]]
+ i -= 1
+ table.insert(debugValueHooksNodes, hooksNode) --[[ ROBLOX CHECK: check if 'debugValueHooksNodes' is an Array ]]
+ else
+ processDebugValues(hooksNode.subHooks, hooksNode)
+ end
+ i += 1
+ end
+ end -- Bubble debug value labels to their custom hook owner.
+ -- If there is no parent hook, just ignore them for now.
+ -- (We may warn about this in the future.)
+ if parentHooksNode ~= nil then
+ -- ROBLOX deviation START: fix length implementation
+ -- if debugValueHooksNodes.length == 1 then
+ if #debugValueHooksNodes == 1 then
+ -- ROBLOX deviation END
+ parentHooksNode.value = debugValueHooksNodes[
+ 1 --[[ ROBLOX adaptation: added 1 to array index ]]
+ ].value
+ -- ROBLOX deviation START: fix length implementation
+ -- elseif
+ -- debugValueHooksNodes.length
+ -- > 1 --[[ ROBLOX CHECK: operator '>' works only if either both arguments are strings or both are a number ]]
+ -- then
+ elseif #debugValueHooksNodes > 1 then
+ -- ROBLOX deviation END
+ parentHooksNode.value = Array.map(debugValueHooksNodes, function(ref0)
+ local value = ref0.value
+ return value
+ end) --[[ ROBLOX CHECK: check if 'debugValueHooksNodes' is an Array ]]
+ end
+ end
+end
+local function inspectHooks(
+ renderFunction: (Props) -> React_Node --[[ ROBLOX CHECK: replaced unhandled characters in identifier. Original identifier: React$Node ]],
+ props: Props,
+ currentDispatcher: CurrentDispatcherRef?
+): HooksTree
+ -- DevTools will pass the current renderer's injected dispatcher.
+ -- Other apps might compile debug hooks as part of their app though.
+ if
+ currentDispatcher == nil --[[ ROBLOX CHECK: loose equality used upstream ]]
+ then
+ currentDispatcher = ReactSharedInternals.ReactCurrentDispatcher
+ end
+ -- ROBLOX deviation START: Luau doesn't understand that currentDispatcher is not nil
+ -- local previousDispatcher = currentDispatcher.current
+ local previousDispatcher = (currentDispatcher :: CurrentDispatcherRef).current
+ -- ROBLOX deviation END
+ local readHookLog;
+ -- ROBLOX deviation START: Luau doesn't understand that currentDispatcher is not nil
+ -- currentDispatcher.current = Dispatcher
+ (currentDispatcher :: CurrentDispatcherRef).current = Dispatcher
+ -- ROBLOX deviation END
+ local ancestorStackError
+ do --[[ ROBLOX COMMENT: try-finally block conversion ]]
+ -- ROBLOX deviation START: doesn't return
+ -- local ok, result, hasReturned = pcall(function()
+ local ok, result = pcall(function()
+ -- ROBLOX deviation END
+ ancestorStackError = Error.new()
+ renderFunction(props)
+ end)
+ do
+ readHookLog = hookLog
+ hookLog = {};
+ -- ROBLOX deviation START: Luau doesn't understand that currentDispatcher is not nil
+ -- currentDispatcher.current = previousDispatcher
+ (currentDispatcher :: CurrentDispatcherRef).current = previousDispatcher
+ -- ROBLOX deviation END
+ end
+ -- ROBLOX deviation START: doesn't return
+ -- if hasReturned then
+ -- return result
+ -- end
+ -- ROBLOX deviation END
+ if not ok then
+ error(result)
+ end
+ end
+ -- ROBLOX deviation START: use dot notation
+ -- local rootStack = ErrorStackParser:parse(ancestorStackError)
+ local rootStack = ErrorStackParser.parse(ancestorStackError)
+ -- ROBLOX deviation END
+ return buildTree(rootStack, readHookLog)
+end
+exports.inspectHooks = inspectHooks
+local function setupContexts(contextMap: Map, any>, fiber: Fiber)
+ local current = fiber
+ -- ROBLOX deviation START: toJSBoolean not needed
+ -- while Boolean.toJSBoolean(current) do
+ while current do
+ -- ROBLOX deviation END
+ if current.tag == ContextProvider then
+ local providerType: ReactProviderType = current.type
+ local context: ReactContext = providerType._context
+ -- ROBLOX deviation START: toJSBoolean not needed
+ -- if not Boolean.toJSBoolean(contextMap:has(context)) then
+ if not contextMap:has(context) then
+ -- ROBLOX deviation END
+ -- Store the current value that we're going to restore later.
+ contextMap:set(context, context._currentValue) -- Set the inner most provider value on the context.
+ context._currentValue = current.memoizedProps.value
+ end
+ end
+ -- ROBLOX deviation START: use return_
+ -- current = current["return"]
+ current = current.return_ :: Fiber
+ -- ROBLOX deviation END
+ end
+end
+local function restoreContexts(contextMap: Map, any>)
+ -- ROBLOX deviation START: use for..in loop
+ -- Array.forEach(contextMap, function(value, context)
+ -- context._currentValue = value
+ -- return context._currentValue
+ -- end) --[[ ROBLOX CHECK: check if 'contextMap' is an Array ]]
+ for _, ref in contextMap do
+ local context, value = ref[1], ref[2]
+ context._currentValue = value
+ end
+ -- ROBLOX deviation END
+end
+local function inspectHooksOfForwardRef(
+ renderFunction: (Props, Ref) -> React_Node --[[ ROBLOX CHECK: replaced unhandled characters in identifier. Original identifier: React$Node ]],
+ props: Props,
+ ref: Ref,
+ currentDispatcher: CurrentDispatcherRef
+): HooksTree
+ local previousDispatcher = currentDispatcher.current
+ local readHookLog
+ currentDispatcher.current = Dispatcher
+ local ancestorStackError
+ do --[[ ROBLOX COMMENT: try-finally block conversion ]]
+ -- ROBLOX deviation START: doesn't return
+ -- local ok, result, hasReturned = pcall(function()
+ local ok, result = pcall(function()
+ -- ROBLOX deviation END
+ ancestorStackError = Error.new()
+ renderFunction(props, ref)
+ end)
+ do
+ readHookLog = hookLog
+ hookLog = {}
+ currentDispatcher.current = previousDispatcher
+ end
+ -- ROBLOX deviation START: doesn't return
+ -- if hasReturned then
+ -- return result
+ -- end
+ -- ROBLOX deviation END
+ if not ok then
+ error(result)
+ end
+ end
+ -- ROBLOX deviation START: use dot notation
+ -- local rootStack = ErrorStackParser:parse(ancestorStackError)
+ local rootStack = ErrorStackParser.parse(ancestorStackError)
+ -- ROBLOX deviation END
+ return buildTree(rootStack, readHookLog)
+end
+-- ROBLOX deviation START: explicit type
+-- local function resolveDefaultProps(Component, baseProps)
+local function resolveDefaultProps(Component, baseProps: Object)
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: toJSBoolean not needed
+ -- if
+ -- Boolean.toJSBoolean(
+ -- if Boolean.toJSBoolean(Component) then Component.defaultProps else Component
+ -- )
+ -- then
+ if typeof(Component) == "table" and Component.defaultProps then
+ -- ROBLOX deviation END
+ -- Resolve default props. Taken from ReactElement
+ local props = Object.assign({}, baseProps)
+ local defaultProps = Component.defaultProps
+ for propName in defaultProps do
+ -- ROBLOX deviation START: needs cast
+ -- if props[tostring(propName)] == nil then
+ -- props[tostring(propName)] = defaultProps[tostring(propName)]
+ if (props :: Object)[propName] == nil then
+ (props :: Object)[propName] = defaultProps[propName]
+ end
+ -- ROBLOX deviation END
+ end
+ return props
+ end
+ return baseProps
+end
+local function inspectHooksOfFiber(fiber: Fiber, currentDispatcher: CurrentDispatcherRef?)
+ -- DevTools will pass the current renderer's injected dispatcher.
+ -- Other apps might compile debug hooks as part of their app though.
+ if
+ currentDispatcher == nil --[[ ROBLOX CHECK: loose equality used upstream ]]
+ then
+ currentDispatcher = ReactSharedInternals.ReactCurrentDispatcher
+ end
+ currentFiber = fiber
+ if
+ fiber.tag ~= FunctionComponent
+ and fiber.tag ~= SimpleMemoComponent
+ and fiber.tag ~= ForwardRef
+ and fiber.tag ~= Block
+ then
+ error(
+ Error.new("Unknown Fiber. Needs to be a function component to inspect hooks.")
+ )
+ end -- Warm up the cache so that it doesn't consume the currentHook.
+ getPrimitiveStackCache()
+ local type_ = fiber.type
+ local props = fiber.memoizedProps
+ if type_ ~= fiber.elementType then
+ props = resolveDefaultProps(type_, props)
+ end -- Set up the current hook so that we can step through and read the
+ -- current state from them.
+ currentHook = fiber.memoizedState :: Hook
+ local contextMap = Map.new()
+ do --[[ ROBLOX COMMENT: try-finally block conversion ]]
+ -- ROBLOX deviation START: doesn't need conditional return
+ -- local ok, result, hasReturned = pcall(function()
+ local ok, result = pcall(function()
+ -- ROBLOX deviation END
+ setupContexts(contextMap, fiber)
+ if fiber.tag == ForwardRef then
+ return inspectHooksOfForwardRef(
+ type_.render,
+ props,
+ fiber.ref,
+ -- ROBLOX deviation START: needs cast
+ -- currentDispatcher
+ currentDispatcher :: CurrentDispatcherRef
+ -- ROBLOX deviation END
+ )
+ end
+ return inspectHooks(type_, props, currentDispatcher)
+ end)
+ do
+ currentHook = nil
+ restoreContexts(contextMap)
+ end
+ -- ROBLOX deviation START: doesn't need conditional return
+ -- if hasReturned then
+ -- return result
+ -- end
+ -- ROBLOX deviation END
+ if not ok then
+ error(result)
+ end
+ -- ROBLOX deviation START: add return
+ return result
+ -- ROBLOX deviation END
+ end
+end
+exports.inspectHooksOfFiber = inspectHooksOfFiber
+return exports
diff --git a/modules/react-debug-tools/src/ReactDebugTools.lua b/modules/react-debug-tools/src/ReactDebugTools.lua
new file mode 100644
index 00000000..a218566e
--- /dev/null
+++ b/modules/react-debug-tools/src/ReactDebugTools.lua
@@ -0,0 +1,20 @@
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.2/packages/react-debug-tools/src/ReactDebugTools.js
+--[[*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ ]]
+local exports = {}
+local reactDebugHooksModule = require(script.Parent.ReactDebugHooks)
+-- ROBLOX deviation START: add re-exporting of types
+export type HooksNode = reactDebugHooksModule.HooksNode
+export type HooksTree = reactDebugHooksModule.HooksTree
+-- ROBLOX deviation END
+local inspectHooks = reactDebugHooksModule.inspectHooks
+local inspectHooksOfFiber = reactDebugHooksModule.inspectHooksOfFiber
+exports.inspectHooks = inspectHooks
+exports.inspectHooksOfFiber = inspectHooksOfFiber
+return exports
diff --git a/modules/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration.spec.lua b/modules/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration.spec.lua
new file mode 100644
index 00000000..05e887f9
--- /dev/null
+++ b/modules/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration.spec.lua
@@ -0,0 +1,643 @@
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.2/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js
+--[[*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @emails react-core
+ * @jest-environment node
+ ]]
+local Packages = script.Parent.Parent.Parent
+local ReactGlobals = require(Packages.ReactGlobals)
+local LuauPolyfill = require(Packages.LuauPolyfill)
+-- ROBLOX deviation START: not needed
+-- local Boolean = LuauPolyfill.Boolean
+-- ROBLOX deviation END
+local Error = LuauPolyfill.Error
+local JestGlobals = require(Packages.Dev.JestGlobals)
+-- ROBLOX deviation START: add additional import
+local afterEach = JestGlobals.afterEach
+-- ROBLOX deviation END
+local beforeEach = JestGlobals.beforeEach
+local describe = JestGlobals.describe
+local expect = JestGlobals.expect
+local it = JestGlobals.it
+local jest = JestGlobals.jest
+
+describe("React hooks DevTools integration", function()
+ local React
+ local ReactDebugTools
+ local ReactTestRenderer
+ local Scheduler
+ local act
+ local overrideHookState
+ local scheduleUpdate
+ local setSuspenseHandler
+ beforeEach(function()
+ -- ROBLOX deviation START: use _G instead of global
+ -- global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
+ ReactGlobals.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
+ -- ROBLOX deviation END
+ inject = function(injected)
+ overrideHookState = injected.overrideHookState
+ scheduleUpdate = injected.scheduleUpdate
+ setSuspenseHandler = injected.setSuspenseHandler
+ end,
+ supportsFiber = true,
+ onCommitFiberRoot = function() end,
+ onCommitFiberUnmount = function() end,
+ }
+ jest.resetModules()
+ -- ROBLOX deviation START: fix requires
+ -- React = require_("react")
+ -- ReactDebugTools = require_("react-debug-tools")
+ -- ReactTestRenderer = require_("react-test-renderer")
+ -- Scheduler = require_("scheduler")
+ ReactTestRenderer = require(Packages.Dev.ReactTestRenderer)
+ React = require(Packages.Dev.React)
+ ReactDebugTools = require(Packages.ReactDebugTools)
+ Scheduler = require(Packages.Dev.Scheduler)
+ -- ROBLOX deviation END
+ act = ReactTestRenderer.act
+ end)
+ -- ROBLOX deviation START: add afterEach to revert global flag
+ afterEach(function()
+ ReactGlobals.__REACT_DEVTOOLS_GLOBAL_HOOK__ = nil
+ end)
+ -- ROBLOX deviation END
+ it("should support editing useState hooks", function()
+ local setCountFn
+ local function MyComponent()
+ -- ROBLOX deviation START: useState returns 2 values
+ -- local count, setCount = table.unpack(React.useState(0), 1, 2)
+ local count, setCount = React.useState(0)
+ -- ROBLOX deviation END
+ setCountFn = setCount
+ -- ROBLOX deviation START: use TextLabel instead
+ -- return React.createElement("div", nil, "count:", count)
+ return React.createElement(
+ "Frame",
+ nil,
+ React.createElement("TextLabel", { Text = "count:" }),
+ React.createElement("TextLabel", { Text = tostring(count) })
+ )
+ -- ROBLOX deviation END
+ end
+ local renderer = ReactTestRenderer.create(React.createElement(MyComponent, nil))
+ expect(renderer:toJSON()).toEqual({
+ -- ROBLOX deviation START: use Frame instead
+ -- type = "div",
+ type = "Frame",
+ -- ROBLOX deviation END
+ props = {},
+ -- ROBLOX deviation START: use TextLabels instead
+ -- children = { "count:", "0" },
+ children = {
+ { type = "TextLabel", props = { Text = "count:" } },
+ { type = "TextLabel", props = { Text = "0" } },
+ },
+ -- ROBLOX deviation END
+ })
+ local fiber = renderer.root:findByType(MyComponent):_currentFiber()
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooksOfFiber(fiber)
+ local tree = ReactDebugTools.inspectHooksOfFiber(fiber)
+ -- ROBLOX deviation END
+ local stateHook = tree[
+ 1 --[[ ROBLOX adaptation: added 1 to array index ]]
+ ]
+ expect(stateHook.isStateEditable).toBe(true)
+ -- ROBLOX deviation START: use ReactGlobals.__DEV__
+ -- if Boolean.toJSBoolean(__DEV__) then
+ if ReactGlobals.__DEV__ then
+ -- ROBLOX deviation END
+ overrideHookState(fiber, stateHook.id, {}, 10)
+ expect(renderer:toJSON()).toEqual({
+ -- ROBLOX deviation START: use Frame instead
+ -- type = "div",
+ type = "Frame",
+ -- ROBLOX deviation END
+ props = {},
+ -- ROBLOX deviation START: use TextLabels instead
+ -- children = { "count:", "10" },
+ children = {
+ { type = "TextLabel", props = { Text = "count:" } },
+ { type = "TextLabel", props = { Text = "10" } },
+ },
+ -- ROBLOX deviation END
+ })
+ act(function()
+ return setCountFn(function(count)
+ return count + 1
+ end)
+ end)
+ expect(renderer:toJSON()).toEqual({
+ -- ROBLOX deviation START: use Frame instead
+ -- type = "div",
+ type = "Frame",
+ -- ROBLOX deviation END
+ props = {},
+ -- ROBLOX deviation START: use TextLabels instead
+ -- children = { "count:", "11" },
+ children = {
+ { type = "TextLabel", props = { Text = "count:" } },
+ { type = "TextLabel", props = { Text = "11" } },
+ },
+ -- ROBLOX deviation END
+ })
+ end
+ end)
+ it("should support editable useReducer hooks", function()
+ local initialData = { foo = "abc", bar = 123 }
+ local function reducer(state, action)
+ local condition_ = action.type
+ if condition_ == "swap" then
+ return { foo = state.bar, bar = state.foo }
+ else
+ error(Error.new())
+ end
+ end
+ local dispatchFn
+ local function MyComponent()
+ -- ROBLOX deviation START: returns 2 values
+ -- local state, dispatch =
+ -- table.unpack(React.useReducer(reducer, initialData), 1, 2)
+ -- ROBLOX deviation END
+ local state, dispatch = React.useReducer(reducer, initialData)
+ dispatchFn = dispatch
+ -- ROBLOX deviation START: use Frame and TextLabels instead
+ -- return React.createElement("div", nil, "foo:", state.foo, ", bar:", state.bar)
+ return React.createElement(
+ "Frame",
+ {},
+ React.createElement("TextLabel", { Text = "foo:" }),
+ React.createElement("TextLabel", { Text = tostring(state.foo) }),
+ React.createElement("TextLabel", { Text = ", bar:" }),
+ React.createElement("TextLabel", { Text = tostring(state.bar) })
+ )
+ -- ROBLOX deviation END
+ end
+ local renderer = ReactTestRenderer.create(React.createElement(MyComponent, nil))
+ expect(renderer:toJSON()).toEqual({
+ -- ROBLOX deviation START: use Frame instead
+ -- type = "div",
+ type = "Frame",
+ -- ROBLOX deviation END
+ props = {},
+ -- ROBLOX deviation START: use TextLabels instead
+ -- children = { "foo:", "abc", ", bar:", "123" },
+ children = {
+ { type = "TextLabel", props = { Text = "foo:" } },
+ { type = "TextLabel", props = { Text = "abc" } },
+ { type = "TextLabel", props = { Text = ", bar:" } },
+ { type = "TextLabel", props = { Text = "123" } },
+ },
+ -- ROBLOX deviation END
+ })
+ local fiber = renderer.root:findByType(MyComponent):_currentFiber()
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooksOfFiber(fiber)
+ local tree = ReactDebugTools.inspectHooksOfFiber(fiber)
+ -- ROBLOX deviation END
+ local reducerHook = tree[
+ 1 --[[ ROBLOX adaptation: added 1 to array index ]]
+ ]
+ expect(reducerHook.isStateEditable).toBe(true)
+ -- ROBLOX deviation START: use ReactGlobals.__DEV__
+ -- if Boolean.toJSBoolean(__DEV__) then
+ if ReactGlobals.__DEV__ then
+ -- ROBLOX deviation END
+ overrideHookState(fiber, reducerHook.id, { "foo" }, "def")
+ expect(renderer:toJSON()).toEqual({
+ -- ROBLOX deviation START: use Frame instead
+ -- type = "div",
+ type = "Frame",
+ -- ROBLOX deviation END
+ props = {},
+ -- ROBLOX deviation START: use TextLabels instead
+ -- children = { "foo:", "def", ", bar:", "123" },
+ children = {
+ { type = "TextLabel", props = { Text = "foo:" } },
+ { type = "TextLabel", props = { Text = "def" } },
+ { type = "TextLabel", props = { Text = ", bar:" } },
+ { type = "TextLabel", props = { Text = "123" } },
+ },
+ -- ROBLOX deviation END
+ })
+ act(function()
+ return dispatchFn({ type = "swap" })
+ end)
+ expect(renderer:toJSON()).toEqual({
+ -- ROBLOX deviation START: use Frame instead
+ -- type = "div",
+ type = "Frame",
+ -- ROBLOX deviation END
+ props = {},
+ -- ROBLOX deviation START: use TextLabels instead
+ -- children = { "foo:", "123", ", bar:", "def" },
+ children = {
+ { type = "TextLabel", props = { Text = "foo:" } },
+ { type = "TextLabel", props = { Text = "123" } },
+ { type = "TextLabel", props = { Text = ", bar:" } },
+ { type = "TextLabel", props = { Text = "def" } },
+ },
+ -- ROBLOX deviation END
+ })
+ end
+ end) -- This test case is based on an open source bug report:
+ -- facebookincubator/redux-react-hook/issues/34#issuecomment-466693787
+ it(
+ "should handle interleaved stateful hooks (e.g. useState) and non-stateful hooks (e.g. useContext)",
+ function()
+ local MyContext = React.createContext(1)
+ local setStateFn
+ local function useCustomHook()
+ local context = React.useContext(MyContext)
+ -- ROBLOX deviation START: returns 2 values
+ -- local state, setState =
+ -- table.unpack(React.useState({ count = context }), 1, 2)
+ local state, setState = React.useState({ count = context })
+ -- ROBLOX deviation END
+ React.useDebugValue(state.count)
+ setStateFn = setState
+ return state.count
+ end
+ local function MyComponent()
+ local count = useCustomHook()
+ -- ROBLOX deviation START: use Frame and TextLabels instead
+ -- return React.createElement("div", nil, "count:", count)
+ return React.createElement(
+ "Frame",
+ nil,
+ React.createElement("TextLabel", { Text = "count:" }),
+ React.createElement("TextLabel", { Text = tostring(count) })
+ )
+ -- ROBLOX deviation END
+ end
+ local renderer =
+ ReactTestRenderer.create(React.createElement(MyComponent, nil))
+ expect(renderer:toJSON()).toEqual({
+ -- ROBLOX deviation START: use Frame instead
+ -- type = "div",
+ type = "Frame",
+ -- ROBLOX deviation END
+ props = {},
+ -- ROBLOX deviation START: use TextLabels instead
+ -- children = { "count:", "1" },
+ children = {
+ { type = "TextLabel", props = { Text = "count:" } },
+ { type = "TextLabel", props = { Text = "1" } },
+ },
+ -- ROBLOX deviation END
+ })
+ local fiber = renderer.root:findByType(MyComponent):_currentFiber()
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooksOfFiber(fiber)
+ local tree = ReactDebugTools.inspectHooksOfFiber(fiber)
+ -- ROBLOX deviation END
+ local stateHook = tree[
+ 1 --[[ ROBLOX adaptation: added 1 to array index ]]
+ ].subHooks[
+ 2 --[[ ROBLOX adaptation: added 1 to array index ]]
+ ]
+ expect(stateHook.isStateEditable).toBe(true)
+ -- ROBLOX deviation START: use ReactGlobals.__DEV__
+ -- if Boolean.toJSBoolean(__DEV__) then
+ if ReactGlobals.__DEV__ then
+ -- ROBLOX deviation END
+ overrideHookState(fiber, stateHook.id, { "count" }, 10)
+ expect(renderer:toJSON()).toEqual({
+ -- ROBLOX deviation START: use Frame instead
+ -- type = "div",
+ type = "Frame",
+ -- ROBLOX deviation END
+ props = {},
+ -- ROBLOX deviation START: use TextLabels instead
+ -- children = { "count:", "10" },
+ children = {
+ { type = "TextLabel", props = { Text = "count:" } },
+ { type = "TextLabel", props = { Text = "10" } },
+ },
+ -- ROBLOX deviation END
+ })
+ act(function()
+ return setStateFn(function(state)
+ return { count = state.count + 1 }
+ end)
+ end)
+ expect(renderer:toJSON()).toEqual({
+ -- ROBLOX deviation START: use Frame instead
+ -- type = "div",
+ type = "Frame",
+ -- ROBLOX deviation END
+ props = {},
+ -- ROBLOX deviation START: use TextLabels instead
+ -- children = { "count:", "11" },
+ children = {
+ { type = "TextLabel", props = { Text = "count:" } },
+ { type = "TextLabel", props = { Text = "11" } },
+ },
+ -- ROBLOX deviation END
+ })
+ end
+ end
+ )
+ it("should support overriding suspense in legacy mode", function()
+ -- ROBLOX deviation START: use ReactGlobals.__DEV__
+ -- if Boolean.toJSBoolean(__DEV__) then
+ if ReactGlobals.__DEV__ then
+ -- ROBLOX deviation END
+ -- Lock the first render
+ setSuspenseHandler(function()
+ return true
+ end)
+ end
+ local function MyComponent()
+ -- ROBLOX deviation START: use TextLabel instead
+ -- return "Done"
+ return React.createElement("TextLabel", { Text = "Done" })
+ -- ROBLOX deviation END
+ end
+ local renderer = ReactTestRenderer.create(React.createElement(
+ -- ROBLOX deviation START: use Frame instead
+ -- "div",
+ "Frame",
+ -- ROBLOX deviation END
+ nil,
+ React.createElement(
+ React.Suspense,
+ -- ROBLOX deviation START: use TextLabel instead
+ -- { fallback = "Loading" },
+ { fallback = React.createElement("TextLabel", { Text = "Loading" }) },
+ -- ROBLOX deviation END
+ React.createElement(MyComponent, nil)
+ )
+ ))
+ local fiber = renderer.root:_currentFiber().child
+ -- ROBLOX deviation START: use ReactGlobals.__DEV__
+ -- if Boolean.toJSBoolean(__DEV__) then
+ if ReactGlobals.__DEV__ then
+ -- ROBLOX deviation END
+ -- First render was locked
+ -- ROBLOX deviation START: use TextLabel instead
+ -- expect(renderer:toJSON().children).toEqual({ "Loading" })
+ expect(renderer:toJSON().children).toEqual({
+ {
+ type = "TextLabel",
+ props = { Text = "Loading" },
+ },
+ })
+ -- ROBLOX deviation END
+ scheduleUpdate(fiber) -- Re-render
+ -- ROBLOX deviation START: use TextLabel instead
+ -- expect(renderer:toJSON().children).toEqual({ "Loading" }) -- Release the lock
+ expect(renderer:toJSON().children).toEqual({
+ {
+ type = "TextLabel",
+ props = { Text = "Loading" },
+ },
+ })
+ -- ROBLOX deviation END
+ setSuspenseHandler(function()
+ return false
+ end)
+ scheduleUpdate(fiber) -- Re-render
+ -- ROBLOX deviation START: use TextLabel instead
+ -- expect(renderer:toJSON().children).toEqual({ "Done" })
+ expect(renderer:toJSON().children).toEqual({
+ {
+ type = "TextLabel",
+ props = { Text = "Done" },
+ },
+ })
+ -- ROBLOX deviation END
+ scheduleUpdate(fiber) -- Re-render
+ -- ROBLOX deviation START: use TextLabel instead
+ -- expect(renderer:toJSON().children).toEqual({ "Done" }) -- Lock again
+ expect(renderer:toJSON().children).toEqual({
+ {
+ type = "TextLabel",
+ props = { Text = "Done" },
+ },
+ })
+ -- ROBLOX deviation END
+ setSuspenseHandler(function()
+ return true
+ end)
+ scheduleUpdate(fiber) -- Re-render
+ -- ROBLOX deviation START: use TextLabel instead
+ -- expect(renderer:toJSON().children).toEqual({ "Loading" }) -- Release the lock again
+ expect(renderer:toJSON().children).toEqual({
+ {
+ type = "TextLabel",
+ props = { Text = "Loading" },
+ },
+ })
+ -- ROBLOX deviation END
+ setSuspenseHandler(function()
+ return false
+ end)
+ scheduleUpdate(fiber) -- Re-render
+ -- ROBLOX deviation START: use TextLabel instead
+ -- expect(renderer:toJSON().children).toEqual({ "Done" }) -- Ensure it checks specific fibers.
+ expect(renderer:toJSON().children).toEqual({
+ {
+ type = "TextLabel",
+ props = { Text = "Done" },
+ },
+ })
+ -- ROBLOX deviation END
+ setSuspenseHandler(function(f)
+ return f == fiber or f == fiber.alternate
+ end)
+ scheduleUpdate(fiber) -- Re-render
+ -- ROBLOX deviation START: use TextLabel instead
+ -- expect(renderer:toJSON().children).toEqual({ "Loading" })
+ expect(renderer:toJSON().children).toEqual({
+ {
+ type = "TextLabel",
+ props = { Text = "Loading" },
+ },
+ })
+ -- ROBLOX deviation END
+ setSuspenseHandler(function(f)
+ return f ~= fiber and f ~= fiber.alternate
+ end)
+ scheduleUpdate(fiber) -- Re-render
+ -- ROBLOX deviation START: use TextLabel instead
+ -- expect(renderer:toJSON().children).toEqual({ "Done" })
+ expect(renderer:toJSON().children).toEqual({
+ {
+ type = "TextLabel",
+ props = { Text = "Done" },
+ },
+ })
+ -- ROBLOX deviation END
+ else
+ -- ROBLOX deviation START: use TextLabel instead
+ -- expect(renderer:toJSON().children).toEqual({ "Done" })
+ expect(renderer:toJSON().children).toEqual({
+ {
+ type = "TextLabel",
+ props = { Text = "Done" },
+ },
+ })
+ -- ROBLOX deviation END
+ end
+ end)
+ it("should support overriding suspense in concurrent mode", function()
+ -- ROBLOX deviation START: add useFakeTimers
+ jest.useFakeTimers()
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: use ReactGlobals.__DEV__
+ -- if Boolean.toJSBoolean(__DEV__) then
+ if ReactGlobals.__DEV__ then
+ -- ROBLOX deviation END
+ -- Lock the first render
+ setSuspenseHandler(function()
+ return true
+ end)
+ end
+ local function MyComponent()
+ -- ROBLOX deviation START: use TextLabel instead
+ -- return "Done"
+ return React.createElement("TextLabel", { Text = "Done" })
+ -- ROBLOX deviation END
+ end
+ local renderer = ReactTestRenderer.create(
+ React.createElement(
+ "div",
+ nil,
+ React.createElement(
+ React.Suspense,
+ -- ROBLOX deviation START: use TextLabel instead
+ -- { fallback = "Loading" },
+ { fallback = React.createElement("TextLabel", { Text = "Loading" }) },
+ -- ROBLOX deviation END
+ React.createElement(MyComponent, nil)
+ )
+ ),
+ { unstable_isConcurrent = true }
+ )
+ expect(Scheduler).toFlushAndYield({}) -- Ensure we timeout any suspense time.
+ jest.advanceTimersByTime(1000)
+ local fiber = renderer.root:_currentFiber().child
+ -- ROBLOX deviation START: use ReactGlobals.__DEV__
+ -- if Boolean.toJSBoolean(__DEV__) then
+ if ReactGlobals.__DEV__ then
+ -- ROBLOX deviation END
+ -- First render was locked
+ -- ROBLOX deviation START: use TextLabel instead
+ -- expect(renderer:toJSON().children).toEqual({ "Loading" })
+ expect(renderer:toJSON().children).toEqual({
+ {
+ type = "TextLabel",
+ props = { Text = "Loading" },
+ },
+ })
+ -- ROBLOX deviation END
+ scheduleUpdate(fiber) -- Re-render
+ -- ROBLOX deviation START: use TextLabel instead
+ -- expect(renderer:toJSON().children).toEqual({ "Loading" }) -- Release the lock
+ expect(renderer:toJSON().children).toEqual({
+ {
+ type = "TextLabel",
+ props = { Text = "Loading" },
+ },
+ })
+ -- ROBLOX deviation END
+ setSuspenseHandler(function()
+ return false
+ end)
+ scheduleUpdate(fiber) -- Re-render
+ -- ROBLOX deviation START: use dot notation
+ -- Scheduler:unstable_flushAll()
+ Scheduler.unstable_flushAll()
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: use TextLabel instead
+ -- expect(renderer:toJSON().children).toEqual({ "Done" })
+ expect(renderer:toJSON().children).toEqual({
+ {
+ type = "TextLabel",
+ props = { Text = "Done" },
+ },
+ })
+ -- ROBLOX deviation END
+ scheduleUpdate(fiber) -- Re-render
+ -- ROBLOX deviation START: use TextLabel instead
+ -- expect(renderer:toJSON().children).toEqual({ "Done" }) -- Lock again
+ expect(renderer:toJSON().children).toEqual({
+ {
+ type = "TextLabel",
+ props = { Text = "Done" },
+ },
+ })
+ -- ROBLOX deviation END
+ setSuspenseHandler(function()
+ return true
+ end)
+ scheduleUpdate(fiber) -- Re-render
+ -- ROBLOX deviation START: use TextLabel instead
+ -- expect(renderer:toJSON().children).toEqual({ "Loading" }) -- Release the lock again
+ expect(renderer:toJSON().children).toEqual({
+ {
+ type = "TextLabel",
+ props = { Text = "Loading" },
+ },
+ })
+ -- ROBLOX deviation END
+ setSuspenseHandler(function()
+ return false
+ end)
+ scheduleUpdate(fiber) -- Re-render
+ -- ROBLOX deviation START: use TextLabel instead
+ -- expect(renderer:toJSON().children).toEqual({ "Done" }) -- Ensure it checks specific fibers.
+ expect(renderer:toJSON().children).toEqual({
+ {
+ type = "TextLabel",
+ props = { Text = "Done" },
+ },
+ })
+ -- ROBLOX deviation END
+ setSuspenseHandler(function(f)
+ return f == fiber or f == fiber.alternate
+ end)
+ scheduleUpdate(fiber) -- Re-render
+ -- ROBLOX deviation START: use TextLabel instead
+ -- expect(renderer:toJSON().children).toEqual({ "Loading" })
+ expect(renderer:toJSON().children).toEqual({
+ {
+ type = "TextLabel",
+ props = { Text = "Loading" },
+ },
+ })
+ -- ROBLOX deviation END
+ setSuspenseHandler(function(f)
+ return f ~= fiber and f ~= fiber.alternate
+ end)
+ scheduleUpdate(fiber) -- Re-render
+ -- ROBLOX deviation START: use TextLabel instead
+ -- expect(renderer:toJSON().children).toEqual({ "Done" })
+ expect(renderer:toJSON().children).toEqual({
+ {
+ type = "TextLabel",
+ props = { Text = "Done" },
+ },
+ })
+ -- ROBLOX deviation END
+ else
+ -- ROBLOX deviation START: use TextLabel instead
+ -- expect(renderer:toJSON().children).toEqual({ "Done" })
+ expect(renderer:toJSON().children).toEqual({
+ {
+ type = "TextLabel",
+ props = { Text = "Done" },
+ },
+ })
+ end
+ -- ROBLOX deviation START: add useRealTimers
+ jest.useRealTimers()
+ -- ROBLOX deviation END
+ end)
+end)
diff --git a/modules/react-debug-tools/src/__tests__/ReactHooksInspection.spec.lua b/modules/react-debug-tools/src/__tests__/ReactHooksInspection.spec.lua
new file mode 100644
index 00000000..f1313643
--- /dev/null
+++ b/modules/react-debug-tools/src/__tests__/ReactHooksInspection.spec.lua
@@ -0,0 +1,497 @@
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.2/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js
+--[[*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @emails react-core
+ * @jest-environment node
+ ]]
+local Packages = script.Parent.Parent.Parent
+-- ROBLOX deviation START: not needed
+-- local LuauPolyfill = require(Packages.LuauPolyfill)
+-- local Boolean = LuauPolyfill.Boolean
+-- ROBLOX deviation END
+local ReactGlobals = require(Packages.ReactGlobals)
+local JestGlobals = require(Packages.Dev.JestGlobals)
+local beforeEach = JestGlobals.beforeEach
+local describe = JestGlobals.describe
+local expect = JestGlobals.expect
+local it = JestGlobals.it
+local jest = JestGlobals.jest
+
+local React
+local ReactDebugTools
+describe("ReactHooksInspection", function()
+ beforeEach(function()
+ jest.resetModules()
+ -- ROBLOX deviation START: fix requires
+ -- React = require_("react")
+ -- ReactDebugTools = require_("react-debug-tools")
+ React = require(Packages.Dev.React)
+ ReactDebugTools = require(Packages.ReactDebugTools)
+ -- ROBLOX deviation END
+ end)
+ it("should inspect a simple useState hook", function()
+ local function Foo(props)
+ -- ROBLOX deviation START: useState returns 2 values
+ -- local state = React.useState("hello world")[1]
+ local state = React.useState("hello world")
+ -- ROBLOX deviation END
+ return React.createElement("div", nil, state)
+ end
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooks(Foo, {})
+ local tree = ReactDebugTools.inspectHooks(Foo, {})
+ -- ROBLOX deviation END
+ expect(tree).toEqual({
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 0,
+ id = 1,
+ -- ROBLOX deviation END
+ name = "State",
+ value = "hello world",
+ subHooks = {},
+ },
+ })
+ end)
+ it("should inspect a simple custom hook", function()
+ local function useCustom(value)
+ local state = React.useState(value)[1]
+ React.useDebugValue("custom hook label")
+ return state
+ end
+ local function Foo(props)
+ local value = useCustom("hello world")
+ return React.createElement("div", nil, value)
+ end
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooks(Foo, {})
+ local tree = ReactDebugTools.inspectHooks(Foo, {})
+ -- ROBLOX deviation END
+ expect(tree).toEqual({
+ {
+ isStateEditable = false,
+ id = nil,
+ name = "Custom",
+ -- ROBLOX deviation START: use ReactGlobals.__DEV__
+ -- value = if Boolean.toJSBoolean(__DEV__) then "custom hook label" else nil,
+ value = if ReactGlobals.__DEV__ then "custom hook label" else nil,
+ -- ROBLOX deviation END
+ subHooks = {
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 0,
+ id = 1,
+ -- ROBLOX deviation END
+ name = "State",
+ value = "hello world",
+ subHooks = {},
+ },
+ },
+ },
+ })
+ end)
+ it("should inspect a tree of multiple hooks", function()
+ local function effect() end
+ local function useCustom(value)
+ -- ROBLOX deviation START: useState returns 2 values
+ -- local state = React.useState(value)[1]
+ local state = React.useState(value)
+ -- ROBLOX deviation END
+ React.useEffect(effect)
+ return state
+ end
+ local function Foo(props)
+ local value1 = useCustom("hello")
+ local value2 = useCustom("world")
+ return React.createElement("div", nil, value1, " ", value2)
+ end
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooks(Foo, {})
+ local tree = ReactDebugTools.inspectHooks(Foo, {})
+ -- ROBLOX deviation END
+ expect(tree).toEqual({
+ {
+ isStateEditable = false,
+ id = nil,
+ name = "Custom",
+ value = nil,
+ subHooks = {
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 0,
+ id = 1,
+ -- ROBLOX deviation END
+ name = "State",
+ subHooks = {},
+ -- ROBLOX deviation START: tell Luau to type this field loosely
+ value = "hello" :: any,
+ -- ROBLOX deviation END
+ },
+ {
+ isStateEditable = false,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 1,
+ id = 2,
+ -- ROBLOX deviation END
+ name = "Effect",
+ subHooks = {},
+ value = effect,
+ },
+ },
+ },
+ {
+ isStateEditable = false,
+ id = nil,
+ name = "Custom",
+ value = nil,
+ subHooks = {
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 2,
+ id = 3,
+ -- ROBLOX deviation END
+ name = "State",
+ -- ROBLOX deviation START: Luau doesn't support mixed arrays
+ -- value = "world",
+ value = "world" :: any,
+ -- ROBLOX deviation END
+ subHooks = {},
+ },
+ {
+ isStateEditable = false,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 3,
+ id = 4,
+ -- ROBLOX deviation END
+ name = "Effect",
+ value = effect,
+ subHooks = {},
+ },
+ },
+ },
+ })
+ end)
+ it("should inspect a tree of multiple levels of hooks", function()
+ local function effect() end
+ local function useCustom(value)
+ local state = React.useReducer(function(s, a)
+ return s
+ -- ROBLOX deviation START: useReducer returns 2 values
+ -- end, value)[1]
+ end, value)
+ -- ROBLOX deviation END
+ React.useEffect(effect)
+ return state
+ end
+ local function useBar(value)
+ local result = useCustom(value)
+ React.useLayoutEffect(effect)
+ return result
+ end
+ local function useBaz(value)
+ React.useLayoutEffect(effect)
+ local result = useCustom(value)
+ return result
+ end
+ local function Foo(props)
+ local value1 = useBar("hello")
+ local value2 = useBaz("world")
+ return React.createElement("div", nil, value1, " ", value2)
+ end
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooks(Foo, {})
+ local tree = ReactDebugTools.inspectHooks(Foo, {})
+ -- ROBLOX deviation END
+ expect(tree).toEqual({
+ {
+ isStateEditable = false,
+ -- ROBLOX deviation START: tell Luau to type this field loosely
+ id = nil :: number?,
+ -- ROBLOX deviation END
+ name = "Bar",
+ value = nil,
+ subHooks = {
+ {
+ isStateEditable = false,
+ -- ROBLOX deviation START: Luau doesn't support mixed arrays
+ -- id = nil,
+ id = nil :: number | nil,
+ -- ROBLOX deviation END
+ name = "Custom",
+ -- ROBLOX deviation START: Luau doesn't support mixed arrays
+ -- value = nil,
+ value = nil :: any,
+ -- ROBLOX deviation END
+ subHooks = {
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 0,
+ id = 1,
+ -- ROBLOX deviation END
+ name = "Reducer",
+ -- ROBLOX deviation START: Luau doesn't support mixed arrays
+ -- value = "hello",
+ value = "hello" :: any,
+ -- ROBLOX deviation END
+ subHooks = {},
+ },
+ {
+ isStateEditable = false,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 1,
+ id = 2,
+ -- ROBLOX deviation END
+ name = "Effect",
+ value = effect,
+ subHooks = {},
+ },
+ },
+ },
+ {
+ isStateEditable = false,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 2,
+ id = 3,
+ -- ROBLOX deviation END
+ name = "LayoutEffect",
+ value = effect,
+ subHooks = {},
+ },
+ },
+ },
+ {
+ isStateEditable = false,
+ id = nil,
+ name = "Baz",
+ value = nil,
+ subHooks = {
+ {
+ isStateEditable = false,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 3,
+ id = 4 :: number?,
+ -- ROBLOX deviation END
+ name = "LayoutEffect",
+ -- ROBLOX deviation START: Luau doesn't support mixed arrays
+ -- value = effect,
+ value = effect :: any,
+ -- ROBLOX deviation END
+ subHooks = {},
+ },
+ {
+ isStateEditable = false,
+ id = nil,
+ name = "Custom",
+ subHooks = {
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 4,
+ id = 5,
+ -- ROBLOX deviation END
+ name = "Reducer",
+ subHooks = {},
+ -- ROBLOX deviation START: Luau doesn't support mixed arrays
+ -- value = "world",
+ value = "world" :: any,
+ -- ROBLOX deviation END
+ },
+ {
+ isStateEditable = false,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 5,
+ id = 6,
+ -- ROBLOX deviation END
+ name = "Effect",
+ subHooks = {},
+ value = effect,
+ },
+ },
+ value = nil,
+ },
+ },
+ },
+ })
+ end)
+ it("should inspect the default value using the useContext hook", function()
+ local MyContext = React.createContext("default")
+ local function Foo(props)
+ local value = React.useContext(MyContext)
+ return React.createElement("div", nil, value)
+ end
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooks(Foo, {})
+ local tree = ReactDebugTools.inspectHooks(Foo, {})
+ -- ROBLOX deviation END
+ expect(tree).toEqual({
+ {
+ isStateEditable = false,
+ id = nil,
+ name = "Context",
+ value = "default",
+ subHooks = {},
+ },
+ })
+ end)
+ it("should support an injected dispatcher", function()
+ local function Foo(props)
+ -- ROBLOX deviation START: useState returns 2 values
+ -- local state = React.useState("hello world")[1]
+ local state = React.useState("hello world")
+ -- ROBLOX deviation END
+ return React.createElement("div", nil, state)
+ end
+ local initial = {}
+ local current = initial
+ local getterCalls = 0
+ local setterCalls = {}
+ -- ROBLOX deviation START: implement getter and setter
+ -- local FakeDispatcherRef = {
+ -- current = function(self)
+ -- getterCalls += 1
+ -- return current
+ -- end,
+ -- current = function(self, value)
+ -- table.insert(setterCalls, value) --[[ ROBLOX CHECK: check if 'setterCalls' is an Array ]]
+ -- current = value
+ -- end,
+ -- }
+ local FakeDispatcherRef = setmetatable({
+ __getters = {
+ current = function(self)
+ print("getting current")
+ getterCalls += 1
+ return current
+ end,
+ },
+ __setters = {
+ current = function(self, value)
+ print("setting current", value)
+ table.insert(setterCalls, value)
+ current = value
+ end,
+ },
+ }, {
+ __index = function(self, key)
+ if typeof(self.__getters[key]) == "function" then
+ return self.__getters[key](self)
+ else
+ return nil
+ end
+ end,
+ __newindex = function(self, key, value)
+ if typeof(self.__setters[key]) == "function" then
+ return self.__setters[key](self, value)
+ else
+ return nil
+ end
+ end,
+ }) :: any
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: aligned to React 18 so we get a hot path optimization in upstream
+ -- expect(function()
+ -- ReactDebugTools:inspectHooks(Foo, {}, FakeDispatcherRef)
+ -- end).toThrow(
+ -- "Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for"
+ -- .. " one of the following reasons:\n"
+ -- .. "1. You might have mismatching versions of React and the renderer (such as React DOM)\n"
+ -- .. "2. You might be breaking the Rules of Hooks\n"
+ -- .. "3. You might have more than one copy of React in the same app\n"
+ -- .. "See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem."
+ -- )
+ local didCatch = false
+ expect(function()
+ -- mock the Error constructor to check the internal of the error instance
+ expect(function()
+ ReactDebugTools.inspectHooks(Foo, {}, FakeDispatcherRef)
+ end).toThrow(
+ -- ROBLOX NOTE: Lua-specific error on nil deref
+ "attempt to index nil with 'useState'"
+ )
+ didCatch = true
+ end).toErrorDev(
+ "Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for"
+ .. " one of the following reasons:\n"
+ .. "1. You might have mismatching versions of React and the renderer (such as React DOM)\n"
+ .. "2. You might be breaking the Rules of Hooks\n"
+ .. "3. You might have more than one copy of React in the same app\n"
+ .. "See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.",
+ { withoutStack = true }
+ )
+ -- avoid false positive if no error was thrown at all
+ expect(didCatch).toBe(true)
+ -- ROBLOX deviation END
+ expect(getterCalls).toBe(1)
+ expect(setterCalls).toHaveLength(2)
+ expect(setterCalls[
+ 1 --[[ ROBLOX adaptation: added 1 to array index ]]
+ -- ROBLOX deviation START: use never instead of ["not"]
+ -- ])["not"].toBe(initial)
+ ]).never.toBe(initial)
+ -- ROBLOX deviation END
+ expect(setterCalls[
+ 2 --[[ ROBLOX adaptation: added 1 to array index ]]
+ ]).toBe(initial)
+ end)
+ describe("useDebugValue", function()
+ it("should be ignored when called outside of a custom hook", function()
+ local function Foo(props)
+ React.useDebugValue("this is invalid")
+ return nil
+ end
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooks(Foo, {})
+ local tree = ReactDebugTools.inspectHooks(Foo, {})
+ -- ROBLOX deviation END
+ expect(tree).toHaveLength(0)
+ end)
+ it("should support an optional formatter function param", function()
+ local function useCustom()
+ React.useDebugValue({ bar = 123 }, function(object)
+ return ("bar:%s"):format(tostring(object.bar))
+ end)
+ React.useState(0)
+ end
+ local function Foo(props)
+ useCustom()
+ return nil
+ end
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooks(Foo, {})
+ local tree = ReactDebugTools.inspectHooks(Foo, {})
+ -- ROBLOX deviation END
+ expect(tree).toEqual({
+ {
+ isStateEditable = false,
+ id = nil,
+ name = "Custom",
+ -- ROBLOX deviation START: use ReactGlobals.__DEV__
+ -- value = if Boolean.toJSBoolean(__DEV__) then "bar:123" else nil,
+ value = if ReactGlobals.__DEV__ then "bar:123" else nil,
+ -- ROBLOX deviation END
+ subHooks = {
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 0,
+ id = 1,
+ -- ROBLOX deviation END
+ name = "State",
+ subHooks = {},
+ value = 0,
+ },
+ },
+ },
+ })
+ end)
+ end)
+end)
diff --git a/modules/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration.spec.lua b/modules/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration.spec.lua
new file mode 100644
index 00000000..360a95b3
--- /dev/null
+++ b/modules/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration.spec.lua
@@ -0,0 +1,1304 @@
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.2/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js
+--[[*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @emails react-core
+ * @jest-environment node
+ ]]
+local Packages = script.Parent.Parent.Parent
+local ReactGlobals = require(Packages.ReactGlobals)
+local LuauPolyfill = require(Packages.LuauPolyfill)
+-- ROBLOX deviation START: not needed
+-- local Boolean = LuauPolyfill.Boolean
+-- ROBLOX deviation END
+-- ROBLOX deviation START: import from dev dependencies
+-- local Promise = require(Packages.Promise)
+local Promise = require(Packages.Dev.Promise)
+-- ROBLOX deviation END
+local JestGlobals = require(Packages.Dev.JestGlobals)
+local beforeEach = JestGlobals.beforeEach
+local describe = JestGlobals.describe
+local expect = JestGlobals.expect
+local it = JestGlobals.it
+local jest = JestGlobals.jest
+-- ROBLOX deviation START: add additional imports
+local String = LuauPolyfill.String
+-- ROBLOX deviation END
+
+local React
+local ReactTestRenderer
+local Scheduler
+local ReactDebugTools
+local act
+describe("ReactHooksInspectionIntegration", function()
+ beforeEach(function()
+ jest.resetModules()
+ -- ROBLOX deviation START: fix requires
+ -- React = require_("react")
+ -- ReactTestRenderer = require_("react-test-renderer")
+ -- Scheduler = require_("scheduler")
+ ReactTestRenderer = require(Packages.Dev.ReactTestRenderer)
+ Scheduler = require(Packages.Dev.Scheduler)
+ React = require(Packages.Dev.React)
+ -- ROBLOX deviation END
+ act = ReactTestRenderer.unstable_concurrentAct
+ -- ROBLOX deviation START: fix requires
+ -- ReactDebugTools = require_("react-debug-tools")
+ ReactDebugTools = require(Packages.ReactDebugTools)
+ -- ROBLOX deviation END
+ end)
+ it("should inspect the current state of useState hooks", function()
+ local useState = React.useState
+ local function Foo(props)
+ -- ROBLOX deviation START: useState returns 2 values
+ -- local state1, setState1 = table.unpack(useState("hello"), 1, 2)
+ -- local state2, setState2 = table.unpack(useState("world"), 1, 2)
+ local state1, setState1 = useState("hello")
+ local state2, setState2 = useState("world")
+ -- ROBLOX deviation END
+ return React.createElement(
+ -- ROBLOX deviation START: use Frame instead
+ -- "div",
+ "Frame",
+ -- ROBLOX deviation END
+ { onMouseDown = setState1, onMouseUp = setState2 },
+ state1,
+ " ",
+ state2
+ )
+ end
+ local renderer =
+ ReactTestRenderer.create(React.createElement(Foo, { prop = "prop" }))
+ local childFiber = renderer.root:findByType(Foo):_currentFiber()
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooksOfFiber(childFiber)
+ local tree = ReactDebugTools.inspectHooksOfFiber(childFiber)
+ -- ROBLOX deviation END
+ expect(tree).toEqual({
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 0,
+ id = 1,
+ -- ROBLOX deviation END
+ name = "State",
+ value = "hello",
+ subHooks = {},
+ },
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 1,
+ id = 2,
+ -- ROBLOX deviation END
+ name = "State",
+ value = "world",
+ subHooks = {},
+ },
+ })
+ local setStateA, setStateB
+ do
+ -- ROBLOX deviation START: use Frame instead
+ -- local ref = renderer.root:findByType("div").props
+ local ref = renderer.root:findByType("Frame").props
+ -- ROBLOX deviation END
+ setStateA, setStateB = ref.onMouseDown, ref.onMouseUp
+ end
+ act(function()
+ return setStateA("Hi")
+ end)
+ childFiber = renderer.root:findByType(Foo):_currentFiber()
+ -- ROBLOX deviation START: use dot notation
+ -- tree = ReactDebugTools:inspectHooksOfFiber(childFiber)
+ tree = ReactDebugTools.inspectHooksOfFiber(childFiber)
+ -- ROBLOX deviation END
+ expect(tree).toEqual({
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 0,
+ id = 1,
+ -- ROBLOX deviation END
+ name = "State",
+ value = "Hi",
+ subHooks = {},
+ },
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 1,
+ id = 2,
+ -- ROBLOX deviation END
+ name = "State",
+ value = "world",
+ subHooks = {},
+ },
+ })
+ act(function()
+ return setStateB("world!")
+ end)
+ childFiber = renderer.root:findByType(Foo):_currentFiber()
+ -- ROBLOX deviation START: use dot notation
+ -- tree = ReactDebugTools:inspectHooksOfFiber(childFiber)
+ tree = ReactDebugTools.inspectHooksOfFiber(childFiber)
+ -- ROBLOX deviation END
+ expect(tree).toEqual({
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 0,
+ id = 1,
+ -- ROBLOX deviation END
+ name = "State",
+ value = "Hi",
+ subHooks = {},
+ },
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 1,
+ id = 2,
+ -- ROBLOX deviation END
+ name = "State",
+ value = "world!",
+ subHooks = {},
+ },
+ })
+ end)
+ it("should inspect the current state of all stateful hooks", function()
+ local outsideRef = React.createRef()
+ local function effect() end
+ local function Foo(props)
+ -- ROBLOX deviation START: useState and useReducer return 2 values
+ -- local state1, setState = table.unpack(React.useState("a"), 1, 2)
+ -- local state2, dispatch = table.unpack(
+ -- React.useReducer(function(s, a)
+ -- return a.value
+ -- end, "b"),
+ -- 1,
+ -- 2
+ -- )
+ local state1, setState = React.useState("a")
+ local state2, dispatch = React.useReducer(function(s, a)
+ return a.value
+ end, "b")
+ -- ROBLOX deviation END
+ local ref = React.useRef("c")
+ React.useLayoutEffect(effect)
+ React.useEffect(effect)
+ React.useImperativeHandle(outsideRef, function()
+ -- Return a function so that jest treats them as non-equal.
+ return function() end
+ end, {})
+ React.useMemo(function()
+ -- ROBLOX deviation START: use string concatenation
+ -- return state1 + state2
+ return state1 .. state2
+ -- ROBLOX deviation END
+ end, { state1 })
+ local function update()
+ act(function()
+ setState("A")
+ end)
+ act(function()
+ dispatch({ value = "B" })
+ end)
+ ref.current = "C"
+ end
+ local memoizedUpdate = React.useCallback(update, {})
+ return React.createElement(
+ -- ROBLOX deviation START: use Frame instead
+ -- "div",
+ "Frame",
+ -- ROBLOX deviation END
+ { onClick = memoizedUpdate },
+ state1,
+ " ",
+ state2
+ )
+ end
+ local renderer
+ act(function()
+ renderer =
+ ReactTestRenderer.create(React.createElement(Foo, { prop = "prop" }))
+ end)
+ local childFiber = renderer.root:findByType(Foo):_currentFiber()
+ -- ROBLOX deviation START: use Frame instead
+ -- local updateStates = renderer.root:findByType("div").props.onClick
+ local updateStates = renderer.root:findByType("Frame").props.onClick
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooksOfFiber(childFiber)
+ local tree = ReactDebugTools.inspectHooksOfFiber(childFiber)
+ -- ROBLOX deviation END
+ expect(tree).toEqual({
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 0,
+ id = 1,
+ -- ROBLOX deviation END
+ name = "State",
+ -- ROBLOX deviation START: tell Luau to type this field loosely
+ value = "a" :: any,
+ -- ROBLOX deviation END
+ subHooks = {},
+ },
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 1,
+ id = 2,
+ -- ROBLOX deviation END
+ name = "Reducer",
+ value = "b",
+ subHooks = {},
+ },
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- { isStateEditable = false, id = 2, name = "Ref", value = "c", subHooks = {} },
+ { isStateEditable = false, id = 3, name = "Ref", value = "c", subHooks = {} },
+ -- ROBLOX deviation END
+ {
+ isStateEditable = false,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 3,
+ id = 4,
+ -- ROBLOX deviation END
+ name = "LayoutEffect",
+ value = effect,
+ subHooks = {},
+ },
+ {
+ isStateEditable = false,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 4,
+ id = 5,
+ -- ROBLOX deviation END
+ name = "Effect",
+ value = effect,
+ subHooks = {},
+ },
+ {
+ isStateEditable = false,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 5,
+ id = 6,
+ -- ROBLOX deviation END
+ name = "ImperativeHandle",
+ value = outsideRef.current,
+ subHooks = {},
+ },
+ {
+ isStateEditable = false,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 6,
+ id = 7,
+ -- ROBLOX deviation END
+ name = "Memo",
+ -- ROBLOX deviation START: useMemo wraps a value
+ -- value = "ab",
+ value = { "ab" },
+ -- ROBLOX deviation END
+ subHooks = {},
+ },
+ {
+ isStateEditable = false,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 7,
+ id = 8,
+ -- ROBLOX deviation END
+ name = "Callback",
+ value = updateStates,
+ subHooks = {},
+ },
+ })
+ updateStates()
+ childFiber = renderer.root:findByType(Foo):_currentFiber()
+ -- ROBLOX deviation START: use dot notation
+ -- tree = ReactDebugTools:inspectHooksOfFiber(childFiber)
+ tree = ReactDebugTools.inspectHooksOfFiber(childFiber)
+ -- ROBLOX deviation END
+ expect(tree).toEqual({
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 0,
+ id = 1,
+ -- ROBLOX deviation END
+ name = "State",
+ -- ROBLOX deviation START: tell Luau to type this field loosely
+ value = "A" :: any,
+ -- ROBLOX deviation END
+ subHooks = {},
+ },
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 1,
+ id = 2,
+ -- ROBLOX deviation END
+ name = "Reducer",
+ value = "B",
+ subHooks = {},
+ },
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- { isStateEditable = false, id = 2, name = "Ref", value = "C", subHooks = {} },
+ { isStateEditable = false, id = 3, name = "Ref", value = "C", subHooks = {} },
+ -- ROBLOX deviation END
+ {
+ isStateEditable = false,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 3,
+ id = 4,
+ -- ROBLOX deviation END
+ name = "LayoutEffect",
+ value = effect,
+ subHooks = {},
+ },
+ {
+ isStateEditable = false,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 4,
+ id = 5,
+ -- ROBLOX deviation END
+ name = "Effect",
+ value = effect,
+ subHooks = {},
+ },
+ {
+ isStateEditable = false,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 5,
+ id = 6,
+ -- ROBLOX deviation END
+ name = "ImperativeHandle",
+ value = outsideRef.current,
+ subHooks = {},
+ },
+ {
+ isStateEditable = false,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 6,
+ id = 7,
+ -- ROBLOX deviation END
+ name = "Memo",
+ -- ROBLOX deviation START: useMemo wraps a value
+ -- value = "Ab",
+ value = { "Ab" },
+ -- ROBLOX deviation END
+ subHooks = {},
+ },
+ {
+ isStateEditable = false,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 7,
+ id = 8,
+ -- ROBLOX deviation END
+ name = "Callback",
+ value = updateStates,
+ subHooks = {},
+ },
+ })
+ end)
+ it("should inspect the value of the current provider in useContext", function()
+ local MyContext = React.createContext("default")
+ local function Foo(props)
+ local value = React.useContext(MyContext)
+ -- ROBLOX deviation START: use Frame instead
+ -- return React.createElement("div", nil, value)
+ return React.createElement("Frame", nil, value)
+ -- ROBLOX deviation END
+ end
+ local renderer = ReactTestRenderer.create(
+ React.createElement(
+ MyContext.Provider,
+ { value = "contextual" },
+ React.createElement(Foo, { prop = "prop" })
+ )
+ )
+ local childFiber = renderer.root:findByType(Foo):_currentFiber()
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- local tree = ReactDebugTools:inspectHooksOfFiber(childFiber)
+ local tree = ReactDebugTools.inspectHooksOfFiber(childFiber)
+ -- ROBLOX deviation END
+ expect(tree).toEqual({
+ {
+ isStateEditable = false,
+ id = nil,
+ name = "Context",
+ value = "contextual",
+ subHooks = {},
+ },
+ })
+ end)
+ it("should inspect forwardRef", function()
+ local function obj() end
+ local Foo = React.forwardRef(function(props, ref)
+ React.useImperativeHandle(ref, function()
+ return obj
+ end)
+ -- ROBLOX deviation START: use Frame instead
+ -- return React.createElement("div", nil)
+ return React.createElement("Frame", nil)
+ -- ROBLOX deviation END
+ end)
+ local ref = React.createRef()
+ local renderer = ReactTestRenderer.create(React.createElement(Foo, { ref = ref }))
+ local childFiber = renderer.root:findByType(Foo):_currentFiber()
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooksOfFiber(childFiber)
+ local tree = ReactDebugTools.inspectHooksOfFiber(childFiber)
+ -- ROBLOX deviation END
+ expect(tree).toEqual({
+ {
+ isStateEditable = false,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 0,
+ id = 1,
+ -- ROBLOX deviation END
+ name = "ImperativeHandle",
+ value = obj,
+ subHooks = {},
+ },
+ })
+ end)
+ it("should inspect memo", function()
+ local function InnerFoo(props)
+ -- ROBLOX deviation START: useState returns 2 values
+ -- local value = React.useState("hello")[1]
+ local value = React.useState("hello")
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: use Frame instead
+ -- return React.createElement("div", nil, value)
+ return React.createElement("Frame", nil, value)
+ -- ROBLOX deviation END
+ end
+ local Foo = React.memo(InnerFoo)
+ local renderer = ReactTestRenderer.create(React.createElement(Foo, nil)) -- TODO: Test renderer findByType is broken for memo. Have to search for the inner.
+ local childFiber = renderer.root:findByType(InnerFoo):_currentFiber()
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooksOfFiber(childFiber)
+ local tree = ReactDebugTools.inspectHooksOfFiber(childFiber)
+ -- ROBLOX deviation END
+ expect(tree).toEqual({
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 0,
+ id = 1,
+ -- ROBLOX deviation END
+ name = "State",
+ value = "hello",
+ subHooks = {},
+ },
+ })
+ end)
+ it("should inspect custom hooks", function()
+ local function useCustom()
+ -- ROBLOX deviation START: useState returns 2 values
+ -- local value = React.useState("hello")[1]
+ local value = React.useState("hello")
+ -- ROBLOX deviation END
+ return value
+ end
+ local function Foo(props)
+ local value = useCustom()
+ -- ROBLOX deviation START: use Frame instead
+ -- return React.createElement("div", nil, value)
+ return React.createElement("Frame", nil, value)
+ -- ROBLOX deviation END
+ end
+ local renderer = ReactTestRenderer.create(React.createElement(Foo, nil))
+ local childFiber = renderer.root:findByType(Foo):_currentFiber()
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooksOfFiber(childFiber)
+ local tree = ReactDebugTools.inspectHooksOfFiber(childFiber)
+ -- ROBLOX deviation END
+ expect(tree).toEqual({
+ {
+ isStateEditable = false,
+ id = nil,
+ name = "Custom",
+ value = nil,
+ subHooks = {
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 0,
+ id = 1,
+ -- ROBLOX deviation END
+ name = "State",
+ value = "hello",
+ subHooks = {},
+ },
+ },
+ },
+ })
+ end) -- @gate experimental
+ -- ROBLOX deviation START: unstable_useTransition is not implemented
+ -- it("should support composite useTransition hook", function()
+ it.skip("should support composite useTransition hook", function()
+ -- ROBLOX deviation END
+ local function Foo(props)
+ -- ROBLOX deviation START: not supported
+ -- React.unstable_useTransition()
+ -- ROBLOX deviation END
+ local memoizedValue = React.useMemo(function()
+ return "hello"
+ end, {})
+ -- ROBLOX deviation START: use Frame instead
+ -- return React.createElement("div", nil, memoizedValue)
+ return React.createElement("Frame", nil, memoizedValue)
+ -- ROBLOX deviation END
+ end
+ local renderer = ReactTestRenderer.create(React.createElement(Foo, nil))
+ local childFiber = renderer.root:findByType(Foo):_currentFiber()
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooksOfFiber(childFiber)
+ local tree = ReactDebugTools.inspectHooksOfFiber(childFiber)
+ -- ROBLOX deviation END
+ expect(tree).toEqual({
+ {
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 0,
+ id = 1,
+ -- ROBLOX deviation END
+ isStateEditable = false,
+ name = "Transition",
+ -- ROBLOX deviation START: tell Luau to type this field loosely
+ value = nil :: any,
+ -- ROBLOX deviation END
+ subHooks = {},
+ },
+ {
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 1,
+ id = 2,
+ -- ROBLOX deviation END
+ isStateEditable = false,
+ name = "Memo",
+ value = "hello",
+ subHooks = {},
+ },
+ })
+ end) -- @gate experimental
+ -- ROBLOX deviation START: unstable_useDeferredValue not implemented
+ -- it("should support composite useDeferredValue hook", function()
+ it.skip("should support composite useDeferredValue hook", function()
+ -- ROBLOX deviation END
+ local function Foo(props)
+ -- ROBLOX deviation START: not implemented
+ -- React.unstable_useDeferredValue("abc", { timeoutMs = 500 })
+ -- ROBLOX deviation END
+ local state = React.useState(function()
+ return "hello"
+ -- ROBLOX deviation START: useState returns 2 values
+ -- end, {})[1]
+ end, {})
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: use Frame instead
+ -- return React.createElement("div", nil, state)
+ return React.createElement("Frame", nil, state)
+ -- ROBLOX deviation END
+ end
+ local renderer = ReactTestRenderer.create(React.createElement(Foo, nil))
+ local childFiber = renderer.root:findByType(Foo):_currentFiber()
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooksOfFiber(childFiber)
+ local tree = ReactDebugTools.inspectHooksOfFiber(childFiber)
+ -- ROBLOX deviation END
+ expect(tree).toEqual({
+ {
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 0,
+ id = 1,
+ -- ROBLOX deviation END
+ isStateEditable = false,
+ name = "DeferredValue",
+ value = "abc",
+ subHooks = {},
+ },
+ {
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 1,
+ id = 2,
+ -- ROBLOX deviation END
+ isStateEditable = true,
+ name = "State",
+ value = "hello",
+ subHooks = {},
+ },
+ })
+ end) -- @gate experimental
+ -- ROBLOX deviation START: unstable_useOpaqueIdentifier not implemented
+ -- it("should support composite useOpaqueIdentifier hook", function()
+ it.skip("should support composite useOpaqueIdentifier hook", function()
+ -- ROBLOX deviation END
+ local function Foo(props)
+ -- ROBLOX deviation START: not implemented
+ -- local id = React.unstable_useOpaqueIdentifier()
+ local id = nil
+ -- ROBLOX deviation END
+ local state = React.useState(function()
+ return "hello"
+ -- ROBLOX deviation START: useState returns 2 values
+ -- end, {})[1]
+ end, {})
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: use Frame instead
+ -- return React.createElement("div", { id = id }, state)
+ return React.createElement("Frame", { id = id }, state)
+ -- ROBLOX deviation END
+ end
+ local renderer = ReactTestRenderer.create(React.createElement(Foo, nil))
+ local childFiber = renderer.root:findByType(Foo):_currentFiber()
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooksOfFiber(childFiber)
+ local tree = ReactDebugTools.inspectHooksOfFiber(childFiber)
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: fix length implementation
+ -- expect(tree.length).toEqual(2)
+ expect(#tree).toEqual(2)
+ -- ROBLOX deviation END
+ expect(tree[
+ 1 --[[ ROBLOX adaptation: added 1 to array index ]]
+ ].id).toEqual(0)
+ expect(tree[
+ 1 --[[ ROBLOX adaptation: added 1 to array index ]]
+ ].isStateEditable).toEqual(false)
+ expect(tree[
+ 1 --[[ ROBLOX adaptation: added 1 to array index ]]
+ ].name).toEqual("OpaqueIdentifier")
+ -- ROBLOX deviation START: use String.startsWith
+ -- expect((tostring(tree[
+ -- 1 --[[ ROBLOX adaptation: added 1 to array index ]]
+ -- ].value) .. ""):startsWith("c_")).toBe(true)
+ expect(String.startsWith(tree[1].value :: string .. "", "c_")).toBe(true)
+ -- ROBLOX deviation END
+ expect(tree[
+ 2 --[[ ROBLOX adaptation: added 1 to array index ]]
+ ]).toEqual({
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 1,
+ id = 2,
+ -- ROBLOX deviation END
+ isStateEditable = true,
+ name = "State",
+ value = "hello",
+ subHooks = {},
+ })
+ end) -- @gate experimental
+ -- ROBLOX deviation START: unstable_useOpaqueIdentifier not implemented
+ -- it("should support composite useOpaqueIdentifier hook in concurrent mode", function()
+ it.skip(
+ "should support composite useOpaqueIdentifier hook in concurrent mode",
+ function()
+ -- ROBLOX deviation END
+ local function Foo(props)
+ -- ROBLOX FIXME: type this correctly when this is supported
+ local id = (React :: any).unstable_useOpaqueIdentifier()
+ local state = React.useState(function()
+ return "hello"
+ -- ROBLOX deviation START: useState returns 2 values
+ -- end, {})[1]
+ end, {})
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: use Frame instead
+ -- return React.createElement("div", { id = id }, state)
+ return React.createElement("Frame", { id = id }, state)
+ -- ROBLOX deviation END
+ end
+ local renderer = ReactTestRenderer.create(
+ React.createElement(Foo, nil),
+ { unstable_isConcurrent = true }
+ )
+ expect(Scheduler).toFlushWithoutYielding()
+ local childFiber = renderer.root:findByType(Foo):_currentFiber()
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooksOfFiber(childFiber)
+ local tree = ReactDebugTools.inspectHooksOfFiber(childFiber)
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: fix length conversion
+ -- expect(tree.length).toEqual(2)
+ expect(#tree).toEqual(2)
+ -- ROBLOX deviation END
+ expect(tree[
+ 1 --[[ ROBLOX adaptation: added 1 to array index ]]
+ ].id).toEqual(0)
+ expect(tree[
+ 1 --[[ ROBLOX adaptation: added 1 to array index ]]
+ ].isStateEditable).toEqual(false)
+ expect(tree[
+ 1 --[[ ROBLOX adaptation: added 1 to array index ]]
+ ].name).toEqual("OpaqueIdentifier")
+ -- ROBLOX deviation START: use String.startsWith
+ -- expect((tostring(tree[
+ -- 1 --[[ ROBLOX adaptation: added 1 to array index ]]
+ -- ].value) .. ""):startsWith("c_")).toBe(true)
+ expect(String.startsWith(tree[1].value :: string .. "", "c_")).toBe(true)
+ -- ROBLOX deviation END
+ expect(tree[
+ 2 --[[ ROBLOX adaptation: added 1 to array index ]]
+ ]).toEqual({
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 1,
+ id = 2,
+ -- ROBLOX deviation END
+ isStateEditable = true,
+ name = "State",
+ value = "hello",
+ subHooks = {},
+ })
+ end
+ )
+ describe("useDebugValue", function()
+ it("should support inspectable values for multiple custom hooks", function()
+ local function useLabeledValue(label)
+ -- ROBLOX deviation START: useState returns 2 values
+ -- local value = React.useState(label)[1]
+ local value = React.useState(label)
+ -- ROBLOX deviation END
+ React.useDebugValue(("custom label %s"):format(tostring(label)))
+ return value
+ end
+ local function useAnonymous(label)
+ -- ROBLOX deviation START: useState returns 2 values
+ -- local value = React.useState(label)[1]
+ local value = React.useState(label)
+ -- ROBLOX deviation END
+ return value
+ end
+ local function Example()
+ useLabeledValue("a")
+ React.useState("b")
+ useAnonymous("c")
+ useLabeledValue("d")
+ return nil
+ end
+ local renderer = ReactTestRenderer.create(React.createElement(Example, nil))
+ local childFiber = renderer.root:findByType(Example):_currentFiber()
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooksOfFiber(childFiber)
+ local tree = ReactDebugTools.inspectHooksOfFiber(childFiber)
+ -- ROBLOX deviation END
+ expect(tree).toEqual({
+ {
+ isStateEditable = false,
+ -- ROBLOX deviation START: tell Luau to type this field loosely
+ id = nil :: number?,
+ -- ROBLOX deviation END
+ name = "LabeledValue",
+ -- ROBLOX deviation START: use ReactGlobals.__DEV__ and cast
+ -- value = if Boolean.toJSBoolean(__DEV__)
+ -- then "custom label a"
+ -- else nil,
+ value = (
+ if ReactGlobals.__DEV__ then "custom label a" else nil
+ ) :: any,
+ -- ROBLOX deviation END
+ subHooks = {
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 0,
+ id = 1,
+ -- ROBLOX deviation END
+ name = "State",
+ value = "a",
+ subHooks = {},
+ },
+ },
+ },
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 1,
+ id = 2,
+ -- ROBLOX deviation END
+ name = "State",
+ value = "b",
+ subHooks = {},
+ },
+ {
+ isStateEditable = false,
+ id = nil,
+ name = "Anonymous",
+ value = nil,
+ subHooks = {
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 2,
+ id = 3,
+ -- ROBLOX deviation END
+ name = "State",
+ value = "c",
+ subHooks = {},
+ },
+ },
+ },
+ {
+ isStateEditable = false,
+ id = nil,
+ name = "LabeledValue",
+ -- ROBLOX deviation START: use ReactGlobals.__DEV__
+ -- value = if Boolean.toJSBoolean(__DEV__)
+ value = if ReactGlobals.__DEV__
+ -- ROBLOX deviation END
+ then "custom label d"
+ else nil,
+ subHooks = {
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 3,
+ id = 4,
+ -- ROBLOX deviation END
+ name = "State",
+ value = "d",
+ subHooks = {},
+ },
+ },
+ },
+ })
+ end)
+ it("should support inspectable values for nested custom hooks", function()
+ local function useInner()
+ React.useDebugValue("inner")
+ React.useState(0)
+ end
+ local function useOuter()
+ React.useDebugValue("outer")
+ useInner()
+ end
+ local function Example()
+ useOuter()
+ return nil
+ end
+ local renderer = ReactTestRenderer.create(React.createElement(Example, nil))
+ local childFiber = renderer.root:findByType(Example):_currentFiber()
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooksOfFiber(childFiber)
+ local tree = ReactDebugTools.inspectHooksOfFiber(childFiber)
+ -- ROBLOX deviation END
+ expect(tree).toEqual({
+ {
+ isStateEditable = false,
+ id = nil,
+ name = "Outer",
+ -- ROBLOX deviation START: use ReactGlobals.__DEV__
+ -- value = if Boolean.toJSBoolean(__DEV__) then "outer" else nil,
+ value = if ReactGlobals.__DEV__ then "outer" else nil,
+ -- ROBLOX deviation END
+ subHooks = {
+ {
+ isStateEditable = false,
+ id = nil,
+ name = "Inner",
+ -- ROBLOX deviation START: use ReactGlobals.__DEV__
+ -- value = if Boolean.toJSBoolean(__DEV__) then "inner" else nil,
+ value = if ReactGlobals.__DEV__ then "inner" else nil,
+ -- ROBLOX deviation END
+ subHooks = {
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 0,
+ id = 1,
+ -- ROBLOX deviation END
+ name = "State",
+ value = 0,
+ subHooks = {},
+ },
+ },
+ },
+ },
+ },
+ })
+ end)
+ it("should support multiple inspectable values per custom hooks", function()
+ local function useMultiLabelCustom()
+ React.useDebugValue("one")
+ React.useDebugValue("two")
+ React.useDebugValue("three")
+ React.useState(0)
+ end
+ local function useSingleLabelCustom(value)
+ React.useDebugValue(("single %s"):format(tostring(value)))
+ React.useState(0)
+ end
+ local function Example()
+ useSingleLabelCustom("one")
+ useMultiLabelCustom()
+ useSingleLabelCustom("two")
+ return nil
+ end
+ local renderer = ReactTestRenderer.create(React.createElement(Example, nil))
+ local childFiber = renderer.root:findByType(Example):_currentFiber()
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- local tree = ReactDebugTools:inspectHooksOfFiber(childFiber)
+ local tree = ReactDebugTools.inspectHooksOfFiber(childFiber)
+ -- ROBLOX deviation END
+ expect(tree).toEqual({
+ {
+ isStateEditable = false,
+ -- ROBLOX deviation START: Luau doesn't support mixed arrays
+ -- id = nil,
+ id = nil :: number | nil,
+ -- ROBLOX deviation END
+ name = "SingleLabelCustom",
+ -- ROBLOX deviation START: use ReactGlobals.__DEV__
+ -- value = if Boolean.toJSBoolean(__DEV__) then "single one" else nil,
+ value = (if ReactGlobals.__DEV__ then "single one" else nil) :: any,
+ -- ROBLOX deviation END
+ subHooks = {
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 0,
+ id = 1,
+ -- ROBLOX deviation END
+ name = "State",
+ value = 0,
+ subHooks = {},
+ },
+ },
+ },
+ {
+ isStateEditable = false,
+ id = nil,
+ name = "MultiLabelCustom",
+ -- ROBLOX deviation START: use ReactGlobals.__DEV__
+ -- value = if Boolean.toJSBoolean(__DEV__)
+ value = if ReactGlobals.__DEV__
+ -- ROBLOX deviation END
+ then { "one", "two", "three" }
+ else nil,
+ subHooks = {
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 1,
+ id = 2,
+ -- ROBLOX deviation END
+ name = "State",
+ value = 0,
+ subHooks = {},
+ },
+ },
+ },
+ {
+ isStateEditable = false,
+ id = nil,
+ name = "SingleLabelCustom",
+ -- ROBLOX deviation START: use ReactGlobals.__DEV__
+ -- value = if Boolean.toJSBoolean(__DEV__) then "single two" else nil,
+ value = if ReactGlobals.__DEV__ then "single two" else nil,
+ -- ROBLOX deviation END
+ subHooks = {
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 2,
+ id = 3,
+ -- ROBLOX deviation END
+ name = "State",
+ value = 0,
+ subHooks = {},
+ },
+ },
+ },
+ })
+ end)
+ it("should ignore useDebugValue() made outside of a custom hook", function()
+ local function Example()
+ React.useDebugValue("this is invalid")
+ return nil
+ end
+ local renderer = ReactTestRenderer.create(React.createElement(Example, nil))
+ local childFiber = renderer.root:findByType(Example):_currentFiber()
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooksOfFiber(childFiber)
+ local tree = ReactDebugTools.inspectHooksOfFiber(childFiber)
+ -- ROBLOX deviation END
+ expect(tree).toHaveLength(0)
+ end)
+ it("should support an optional formatter function param", function()
+ local function useCustom()
+ React.useDebugValue({ bar = 123 }, function(object)
+ return ("bar:%s"):format(tostring(object.bar))
+ end)
+ React.useState(0)
+ end
+ local function Example()
+ useCustom()
+ return nil
+ end
+ local renderer = ReactTestRenderer.create(React.createElement(Example, nil))
+ local childFiber = renderer.root:findByType(Example):_currentFiber()
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooksOfFiber(childFiber)
+ local tree = ReactDebugTools.inspectHooksOfFiber(childFiber)
+ -- ROBLOX deviation END
+ expect(tree).toEqual({
+ {
+ isStateEditable = false,
+ id = nil,
+ name = "Custom",
+ -- ROBLOX deviation START: use ReactGlobals.__DEV__
+ -- value = if Boolean.toJSBoolean(__DEV__) then "bar:123" else nil,
+ value = if ReactGlobals.__DEV__ then "bar:123" else nil,
+ -- ROBLOX deviation END
+ subHooks = {
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 0,
+ id = 1,
+ -- ROBLOX deviation END
+ name = "State",
+ subHooks = {},
+ value = 0,
+ },
+ },
+ },
+ })
+ end)
+ end)
+ -- ROBLOX deviation START: defaultProps not supported for function components yet
+ -- it("should support defaultProps and lazy", function()
+ it.skip("should support defaultProps and lazy", function()
+ -- ROBLOX deviation END
+ return Promise.resolve():andThen(function()
+ -- ROBLOX deviation START: defaultProps not supported for function components yet
+ -- local Suspense = React.Suspense
+ -- local function Foo(props)
+ -- local value = React.useState(props.defaultValue:substr(0, 3))[1]
+ -- return React.createElement("div", nil, value)
+ -- end
+ -- Foo.defaultProps = { defaultValue = "default" }
+ -- local function fakeImport(result)
+ -- return Promise.resolve():andThen(function()
+ -- return { default = result }
+ -- end)
+ -- end
+ -- local LazyFoo = React.lazy(function()
+ -- return fakeImport(Foo)
+ -- end)
+ -- local renderer = ReactTestRenderer.create(
+ -- React.createElement(
+ -- Suspense,
+ -- { fallback = "Loading..." },
+ -- React.createElement(LazyFoo, nil)
+ -- )
+ -- )
+ -- LazyFoo:expect()
+ -- Scheduler:unstable_flushAll()
+ -- local childFiber = renderer.root:_currentFiber()
+ -- local tree = ReactDebugTools:inspectHooksOfFiber(childFiber)
+ -- expect(tree).toEqual({
+ -- {
+ -- isStateEditable = true,
+ -- id = 0,
+ -- name = "State",
+ -- value = "def",
+ -- subHooks = {},
+ -- },
+ -- })
+ -- ROBLOX deviation END
+ end)
+ end)
+ it("should support an injected dispatcher", function()
+ local function Foo(props)
+ -- ROBLOX deviation START: useState returns 2 values
+ -- local state = React.useState("hello world")[1]
+ local state = React.useState("hello world")
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: use Frame instead
+ -- return React.createElement("div", nil, state)
+ return React.createElement("Frame", nil, state)
+ -- ROBLOX deviation END
+ end
+ local initial = {}
+ local current = initial
+ local getterCalls = 0
+ local setterCalls = {}
+ -- ROBLOX deviation START: implement getter and setter
+ -- local FakeDispatcherRef = {
+ -- current = function(self)
+ -- getterCalls += 1
+ -- return current
+ -- end,
+ -- current = function(self, value)
+ -- table.insert(setterCalls, value) --[[ ROBLOX CHECK: check if 'setterCalls' is an Array ]]
+ -- current = value
+ -- end,
+ -- }
+ local FakeDispatcherRef = setmetatable({
+ __getters = {
+ current = function(self)
+ getterCalls += 1
+ return current
+ end,
+ },
+ __setters = {
+ current = function(self, value)
+ table.insert(setterCalls, value)
+ current = value
+ end,
+ },
+ }, {
+ __index = function(self, key)
+ if typeof(self.__getters[key]) == "function" then
+ return self.__getters[key](self)
+ else
+ return nil
+ end
+ end,
+ __newindex = function(self, key, value)
+ if typeof(self.__setters[key]) == "function" then
+ return self.__setters[key](self, value)
+ else
+ return nil
+ end
+ end,
+ }) :: any
+ -- ROBLOX deviation END
+ local renderer = ReactTestRenderer.create(React.createElement(Foo, nil))
+ local childFiber = renderer.root:_currentFiber()
+ expect(function()
+ -- ROBLOX deviation START: use dot notation
+ -- ReactDebugTools:inspectHooksOfFiber(childFiber, FakeDispatcherRef)
+ ReactDebugTools.inspectHooksOfFiber(childFiber, FakeDispatcherRef)
+ -- ROBLOX deviation END
+ end).toThrow(
+ "Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for"
+ .. " one of the following reasons:\n"
+ .. "1. You might have mismatching versions of React and the renderer (such as React DOM)\n"
+ .. "2. You might be breaking the Rules of Hooks\n"
+ .. "3. You might have more than one copy of React in the same app\n"
+ .. "See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem."
+ )
+ expect(getterCalls).toBe(1)
+ expect(setterCalls).toHaveLength(2)
+ expect(setterCalls[
+ 1 --[[ ROBLOX adaptation: added 1 to array index ]]
+ -- ROBLOX deviation START: use never instead of not
+ -- ])["not"].toBe(initial)
+ ]).never.toBe(initial)
+ -- ROBLOX deviation END
+ expect(setterCalls[
+ 2 --[[ ROBLOX adaptation: added 1 to array index ]]
+ ]).toBe(initial)
+ end) -- This test case is based on an open source bug report:
+ -- facebookincubator/redux-react-hook/issues/34#issuecomment-466693787
+ it("should properly advance the current hook for useContext", function()
+ local MyContext = React.createContext(1)
+ local incrementCount
+ local function Foo(props)
+ local context = React.useContext(MyContext)
+ -- ROBLOX deviation START: useState returns 2 values
+ -- local data, setData = table.unpack(React.useState({ count = context }), 1, 2)
+ local data, setData = React.useState({ count = context })
+ -- ROBLOX deviation END
+ incrementCount = function()
+ return setData(function(ref0)
+ local count = ref0.count
+ return { count = count + 1 }
+ end)
+ end
+ -- ROBLOX deviation START: use FRame instead
+ -- return React.createElement("div", nil, "count: ", data.count)
+ return React.createElement("Frame", nil, "count: ", data.count)
+ -- ROBLOX deviation END
+ end
+ local renderer = ReactTestRenderer.create(React.createElement(Foo, nil))
+ expect(renderer:toJSON()).toEqual({
+ -- ROBLOX deviation START: use Frame instead
+ -- type = "div",
+ type = "Frame",
+ -- ROBLOX deviation END
+ props = {},
+ children = { "count: ", "1" },
+ })
+ act(incrementCount)
+ expect(renderer:toJSON()).toEqual({
+ -- ROBLOX deviation START: use Frame instead
+ -- type = "div",
+ type = "Frame",
+ -- ROBLOX deviation END
+ props = {},
+ children = { "count: ", "2" },
+ })
+ local childFiber = renderer.root:_currentFiber()
+ -- ROBLOX deviation START: use dot notation
+ -- local tree = ReactDebugTools:inspectHooksOfFiber(childFiber)
+ local tree = ReactDebugTools.inspectHooksOfFiber(childFiber)
+ -- ROBLOX deviation END
+ expect(tree).toEqual({
+ {
+ isStateEditable = false,
+ -- ROBLOX deviation START: Luau doesn't support mixed arrays
+ -- id = nil,
+ id = nil :: number | nil,
+ -- ROBLOX deviation END
+ name = "Context",
+ -- ROBLOX deviation START: Luau doesn't support mixed arrays
+ -- value = 1,
+ value = 1 :: any,
+ -- ROBLOX deviation END
+ subHooks = {},
+ },
+ {
+ isStateEditable = true,
+ -- ROBLOX deviation START: adjust for 1-based indexing
+ -- id = 0,
+ id = 1,
+ -- ROBLOX deviation END
+ name = "State",
+ value = { count = 2 },
+ subHooks = {},
+ },
+ })
+ end)
+ -- ROBLOX deviation START: no experimental features
+ -- if Boolean.toJSBoolean(__EXPERIMENTAL__) then
+ -- it("should support composite useMutableSource hook", function()
+ -- local mutableSource = React.unstable_createMutableSource({}, function()
+ -- return 1
+ -- end)
+ -- local function Foo(props)
+ -- React.unstable_useMutableSource(mutableSource, function()
+ -- return "snapshot"
+ -- end, function() end)
+ -- React.useMemo(function()
+ -- return "memo"
+ -- end, {})
+ -- return React.createElement("div", nil)
+ -- end
+ -- local renderer = ReactTestRenderer.create(React.createElement(Foo, nil))
+ -- local childFiber = renderer.root:findByType(Foo):_currentFiber()
+ -- local tree = ReactDebugTools:inspectHooksOfFiber(childFiber)
+ -- expect(tree).toEqual({
+ -- {
+ -- id = 0,
+ -- isStateEditable = false,
+ -- name = "MutableSource",
+ -- value = "snapshot",
+ -- subHooks = {},
+ -- },
+ -- {
+ -- id = 1,
+ -- isStateEditable = false,
+ -- name = "Memo",
+ -- value = "memo",
+ -- subHooks = {},
+ -- },
+ -- })
+ -- end)
+ -- end
+ -- ROBLOX deviation END
+end)
diff --git a/modules/react-debug-tools/src/init.lua b/modules/react-debug-tools/src/init.lua
new file mode 100644
index 00000000..2c07d670
--- /dev/null
+++ b/modules/react-debug-tools/src/init.lua
@@ -0,0 +1,19 @@
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.2/packages/react-debug-tools/index.js
+--[[*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ ]]
+-- ROBLOX deviation START: simplify and re-export types
+-- local Packages --[[ ROBLOX comment: must define Packages module ]]
+-- local LuauPolyfill = require(Packages.LuauPolyfill)
+-- local Object = LuauPolyfill.Object
+-- local exports = {}
+-- Object.assign(exports, require(script.src.ReactDebugTools))
+-- return exports
+local reactDebugToolsModule = require(script.ReactDebugTools)
+export type HooksNode = reactDebugToolsModule.HooksNode
+export type HooksTree = reactDebugToolsModule.HooksTree
+return reactDebugToolsModule
+-- ROBLOX deviation END
diff --git a/modules/react-devtools-core/.luaurc b/modules/react-devtools-core/.luaurc
new file mode 100644
index 00000000..89bffcfa
--- /dev/null
+++ b/modules/react-devtools-core/.luaurc
@@ -0,0 +1,3 @@
+{
+ "languageMode": "strict"
+}
\ No newline at end of file
diff --git a/modules/react-devtools-core/.robloxrc b/modules/react-devtools-core/.robloxrc
new file mode 100644
index 00000000..8f779576
--- /dev/null
+++ b/modules/react-devtools-core/.robloxrc
@@ -0,0 +1,8 @@
+{
+ "language": {
+ "mode": "strict"
+ },
+ "lint": {
+ "*": "enabled"
+ }
+}
\ No newline at end of file
diff --git a/modules/react-devtools-core/README.md b/modules/react-devtools-core/README.md
new file mode 100644
index 00000000..032886e7
--- /dev/null
+++ b/modules/react-devtools-core/README.md
@@ -0,0 +1,24 @@
+# `react-devtools-core`
+
+A React DevTools bridge implementation for React Lua. The implementation of this package deviates heavily from the upstream version.
+
+## API
+
+Requiring this package is similar to requiring `react-devtools`, but provides several configurable options. Unlike `react-devtools`, requiring `react-devtools-core` doesn't connect immediately but instead exports a function:
+
+```luau
+local ReactDevtoolsCore = require(Packages.ReactDevtoolsCore)
+local connectToDevtools = ReactDevtoolsCore.connectToDevtools
+connectToDevTools(config)
+```
+
+Run `connectToDevTools()` in the same context as React to set up a connection to DevTools.
+Be sure to run this function before importing *any* React package -- e.g. `react`, `react-roblox`.
+
+The `config` object may contain:
+
+- `host: string` (defaults to "localhost") - Websocket will connect to this host.
+- `port: number` (defaults to `8097`) - Websocket will connect to this port.
+- `useHttps: boolean` (defaults to `false`) - Websocket should use a secure protocol (wss).
+- `resolveRNStyle: (style: number) => ?Object` - Used by the React Native style plug-in.
+- `isAppActive: () => boolean` - If provided, DevTools will poll this method and wait until it returns true before connecting to React.
diff --git a/modules/react-devtools-core/default.project.json b/modules/react-devtools-core/default.project.json
new file mode 100644
index 00000000..ba42b45a
--- /dev/null
+++ b/modules/react-devtools-core/default.project.json
@@ -0,0 +1,6 @@
+{
+ "name": "ReactDevtoolsCore",
+ "tree": {
+ "$path": "src"
+ }
+}
diff --git a/modules/react-devtools-core/rotriever.toml b/modules/react-devtools-core/rotriever.toml
new file mode 100644
index 00000000..c2d8c1e1
--- /dev/null
+++ b/modules/react-devtools-core/rotriever.toml
@@ -0,0 +1,16 @@
+[package]
+name = "ReactDevtoolsCore"
+version = { workspace = true }
+authors = { workspace = true }
+publish = true
+content_root = "src"
+files = ["*", "!**/__tests__/**"]
+
+[dependencies]
+LuauPolyfill = { workspace = true }
+ReactDevtoolsShared = { path = "../react-devtools-shared" }
+ReactGlobals = { path = "../react-globals" }
+ReactTelemetry = { path = "../react-telemetry" }
+
+[dev_dependencies]
+JestGlobals = { workspace = true }
diff --git a/modules/react-devtools-core/src/backend.lua b/modules/react-devtools-core/src/backend.lua
new file mode 100644
index 00000000..6c16cbd9
--- /dev/null
+++ b/modules/react-devtools-core/src/backend.lua
@@ -0,0 +1,268 @@
+-- Based on: https://github.com/facebook/react/blob/12adaffef7105e2714f82651ea51936c563fe15c/packages/react-devtools-core/src/backend.js
+
+local HttpService = game:GetService("HttpService")
+local WebSocketService = game:GetService("WebSocketService")
+
+local Packages = script.Parent.Parent
+local ReactGlobals = require(Packages.ReactGlobals)
+local ReactDevtoolsShared = require(Packages.ReactDevtoolsShared)
+local ReactTelemetry = require(Packages.ReactTelemetry)
+local LuauPolyfill = require(Packages.LuauPolyfill)
+
+local Object = LuauPolyfill.Object
+
+local Agent = ReactDevtoolsShared.backend.agent
+local Bridge = ReactDevtoolsShared.bridge
+local installHook = ReactDevtoolsShared.hook.installHook
+local initBackend = ReactDevtoolsShared.backend.initBackend
+local __DEBUG__ = ReactDevtoolsShared.constants.__DEBUG__
+local getDefaultComponentFilters = ReactDevtoolsShared.utils.getDefaultComponentFilters
+
+type BackendBridge = ReactDevtoolsShared.BackendBridge
+type ComponentFilter = ReactDevtoolsShared.ComponentFilter
+type DevtoolsHook = ReactDevtoolsShared.DevtoolsHook
+
+local serializeTable = require(script.Parent.utils.serializeTable)
+
+-- ROBLOX deviation: In order to support launching DevTools after the client has
+-- started, the renderer needs to be injected before any other scripts. The hook
+-- will look for the presence of a global __REACT_DEVTOOLS_ATTACH__ and attach
+-- an injected renderer early.
+require(script.Parent.setupAttachHook)
+
+type Array = { T }
+
+export type ConnectOptions = {
+ host: string?,
+ port: number?,
+ useHttps: boolean?,
+ isAppActive: (() -> boolean)?,
+ -- ROBLOX Deviation: Expose an option to start profiling as soon as we
+ -- setup the DevTools hook.
+ profileOnStart: boolean?,
+}
+
+local hook = installHook(ReactGlobals)
+local savedComponentFilters: Array = getDefaultComponentFilters()
+
+local function debugPrint(methodName: string, ...: any)
+ if __DEBUG__ then
+ print(`[core/backend] {methodName}`, ...)
+ end
+end
+
+local function connectToDevtools(options_: ConnectOptions?)
+ if hook == nil then
+ -- Devtools wasn't injected into this context
+ return
+ end
+
+ local options: ConnectOptions = options_ or {}
+ local host = options.host or "localhost"
+ local useHttps = if options.useHttps == nil then false else options.useHttps
+ local port = options.port or 8097
+ local isAppActive = options.isAppActive or function()
+ return true
+ end
+
+ local protocol = if useHttps then "wss" else "ws"
+ local retryTimeoutThread: thread? = nil
+
+ local function scheduleRetry()
+ if retryTimeoutThread == nil then
+ retryTimeoutThread = task.delay(2, function()
+ connectToDevtools(options)
+ end)
+ end
+ end
+
+ if not isAppActive() then
+ scheduleRetry()
+ return
+ end
+
+ local bridge: BackendBridge? = nil
+ local agent
+
+ local messageListeners = {}
+ local uri = `{protocol}://{host}:{port}`
+
+ local function handleClose()
+ debugPrint("Socket.on('close')")
+
+ if bridge ~= nil then
+ bridge:shutdown()
+ end
+
+ ReactTelemetry.reportFailedDevtoolsConnection("socket_closed")
+ scheduleRetry()
+ end
+
+ local function handleMessage(event: any)
+ local success, data = pcall(function()
+ if type(event) == "string" then
+ local data = HttpService:JSONDecode(event)
+ debugPrint("Socket.on('message')", data)
+ return data
+ else
+ error(`Bad data received from socket: {event} (of type {typeof(event)})`)
+ end
+ end)
+
+ if not success then
+ error(
+ `[React DevTools] Failed to parse JSON: {event.data} (got error: {data})`
+ )
+ end
+
+ for _, fn in messageListeners do
+ local success, result = pcall(fn :: any, data)
+ if not success then
+ error(
+ `[React DevTools] Error calling listener with data: {data}\n{result}`
+ )
+ end
+ end
+ end
+
+ local success, socket = pcall(function()
+ return WebSocketService:CreateClient(uri)
+ end)
+
+ if success == false then
+ warn(
+ `[React DevTools] Could not connect to DevTools. Attempted to connect to "{uri}" ({socket})`
+ )
+ ReactTelemetry.reportFailedDevtoolsConnection("create_client_failed")
+ scheduleRetry()
+ return
+ end
+
+ socket.Closed:Connect(handleClose)
+ socket.MessageReceived:Connect(handleMessage)
+ socket.Opened:Connect(function()
+ bridge = Bridge.new({
+ listen = function(fn)
+ table.insert(messageListeners, fn)
+ return function()
+ local index = table.find(messageListeners, fn)
+ if index then
+ table.remove(messageListeners, index)
+ end
+ end
+ end,
+ send = function(event: string, payload: any, transferable: Array?)
+ if socket.ConnectionState == Enum.WebSocketState.Open then
+ debugPrint("wall.send()", event, payload)
+
+ payload = serializeTable(payload)
+
+ local SERDE_TO_NULL = newproxy()
+
+ if event == "inspectedElement" then
+ if payload.type == "full-data" then
+ local defaultValue = {
+ displayName = SERDE_TO_NULL,
+ context = SERDE_TO_NULL,
+ hooks = SERDE_TO_NULL,
+ props = SERDE_TO_NULL,
+ state = SERDE_TO_NULL,
+ key = SERDE_TO_NULL,
+ owners = SERDE_TO_NULL,
+ source = SERDE_TO_NULL,
+ rootType = SERDE_TO_NULL,
+ rendererPackageName = SERDE_TO_NULL,
+ rendererVersion = SERDE_TO_NULL,
+ }
+
+ payload.value = Object.assign(defaultValue, payload.value)
+ end
+ end
+
+ socket:Send(HttpService:JSONEncode({
+ event = event,
+ payload = payload,
+ }))
+ else
+ debugPrint(
+ "wall.send()",
+ "Shutting down bridge because of closed WebSocket connection"
+ )
+
+ if bridge ~= nil then
+ bridge:shutdown()
+ end
+
+ scheduleRetry()
+ end
+ end,
+ })
+
+ assert(bridge, "Luau")
+
+ bridge:addListener(
+ "inspectElement",
+ function(data: { id: number, rendererID: number })
+ local id = data.id
+ local rendererId = data.rendererID
+
+ if agent then
+ local renderer = agent._rendererInterfaces[rendererId]
+ if renderer ~= nil then
+ local nodes = renderer.findNativeNodesForFiberID(id)
+ if nodes ~= nil and next(nodes) ~= nil then
+ local node = nodes[1]
+ agent:emit("showNativeHighlight", node)
+ end
+ end
+ end
+ end
+ )
+
+ bridge:addListener(
+ "updateComponentFilters",
+ function(componentFilters: Array)
+ -- Save filter changes in memory, in case DevTools is reloaded.
+ -- In that case, the renderer will already be using the updated
+ -- values. We'll lose these in between backend reloads but that
+ -- can't be helped.
+ savedComponentFilters = componentFilters
+ end
+ )
+
+ -- The renderer interface doesn't read saved component filters directly,
+ -- because they are generally stored in localStorage within the context
+ -- of the extension. Because of this it relies on the extension to pass
+ -- filters. In the case of the standalone DevTools being used with a
+ -- website, saved filters are injected along with the backend script tag
+ -- so we shouldn't override them here. This injection strategy doesn't
+ -- work for React Native though. Ideally the backend would save the
+ -- filters itself, but RN doesn't provide a sync storage solution. So
+ -- for now we just fall back to using the default filters...
+ if ReactGlobals.__REACT_DEVTOOLS_COMPONENT_FILTERS__ == nil then
+ bridge:send("overrideComponentFilters", savedComponentFilters)
+ end
+
+ agent = Agent.new(bridge) :: any
+ agent:addListener("shutdown", function()
+ -- If we received 'shutdown' from `agent`, we assume the `bridge` is
+ -- already shutting down, and that caused the 'shutdown' event on
+ -- the `agent`, so we don't need to call `bridge.shutdown()` here.
+ hook:emit("shutdown")
+ end)
+
+ initBackend(hook, agent :: any, ReactGlobals)
+
+ ReactTelemetry.reportNewDevtoolsConnection()
+
+ -- ROBLOX Deviation: Expose an option to start profiling as soon as we
+ -- setup the DevTools hook.
+ if options.profileOnStart then
+ agent:startProfiling()
+ end
+ end)
+end
+
+return {
+ connectToDevtools = connectToDevtools,
+}
diff --git a/modules/react-devtools-core/src/init.lua b/modules/react-devtools-core/src/init.lua
new file mode 100644
index 00000000..d15d4ae7
--- /dev/null
+++ b/modules/react-devtools-core/src/init.lua
@@ -0,0 +1,5 @@
+local backend = require(script.backend)
+
+return {
+ backend = backend,
+}
diff --git a/modules/react-devtools-core/src/setupAttachHook.lua b/modules/react-devtools-core/src/setupAttachHook.lua
new file mode 100644
index 00000000..bd4fee67
--- /dev/null
+++ b/modules/react-devtools-core/src/setupAttachHook.lua
@@ -0,0 +1,26 @@
+-- Based on: https://github.com/facebook/react/blob/8e5adfbd7e605bda9c5e96c10e015b3dc0df688e/packages/react-devtools-extensions/src/renderer.js
+
+--[[
+ In order to support launching DevTools after the client has started, the
+ renderer needs to be injected before any other scripts. The hook will look
+ for the presence of a global __REACT_DEVTOOLS_ATTACH__ and attach an
+ injected renderer early.
+]]
+
+local Packages = script.Parent.Parent
+local ReactGlobals = require(Packages.ReactGlobals)
+local ReactDevtoolsShared = require(Packages.ReactDevtoolsShared)
+
+local attach
+ReactGlobals.__REACT_DEVTOOLS_ATTACH__ = function(...)
+ -- Importing the renderer module immediately causes React to initialize
+ -- prematurely and error. We import lazily here to avoid this, because
+ -- React will be initialized by the time this function is called.
+ if attach == nil then
+ attach = ReactDevtoolsShared.backend.getRendererLazy().attach
+ end
+
+ return attach(...)
+end
+
+return nil
diff --git a/modules/react-devtools-core/src/utils/serializeTable.lua b/modules/react-devtools-core/src/utils/serializeTable.lua
new file mode 100644
index 00000000..cd5561a1
--- /dev/null
+++ b/modules/react-devtools-core/src/utils/serializeTable.lua
@@ -0,0 +1,106 @@
+local HttpService = game:GetService("HttpService")
+
+type Record = { [string]: any }
+
+local ENCODE_TO_NULL = newproxy()
+
+--- Serializes a table such that it can be encoded as JSON via HttpService.
+local function serializeTable(tbl: Record): Record
+ local seen = {}
+
+ local function visitPropsRecursive(parent: Record): any
+ if type(parent) ~= "table" then
+ return parent
+ end
+
+ -- Break cyclic references
+ if seen[parent] then
+ return nil
+ end
+ seen[parent] = true
+
+ local newParent = {}
+ for key, value in parent do
+ if type(value) == "table" then
+ local newValue = visitPropsRecursive(value)
+ newParent[key] = newValue
+ elseif typeof(value) == "UDim" then
+ newParent[key] = `UDim({value.Scale}, {value.Offset})`
+ elseif typeof(value) == "UDim2" then
+ newParent[key] =
+ `UDim2({value.X.Scale}, {value.X.Offset}, {value.Y.Scale}, {value.Y.Offset})`
+ elseif typeof(value) == "Vector2" then
+ newParent[key] = `Vector2({value.X}, {value.Y})`
+ elseif typeof(value) == "Vector3" then
+ newParent[key] = `Vector3({value.X}, {value.Y}, {value.Z})`
+ elseif typeof(value) == "CFrame" then
+ newParent[key] = `CFrame({tostring(value)})`
+ elseif typeof(value) == "Color3" then
+ newParent[key] = `Color3({value.R}, {value.G}, {value.B})`
+ elseif typeof(value) == "Rect" then
+ newParent[key] =
+ `Rect({value.Min.X}, {value.Min.Y}, {value.Max.X}, {value.Max.Y})`
+ elseif typeof(value) == "EnumItem" then
+ newParent[key] = `EnumItem({value.EnumType}, {value.Name})`
+ elseif typeof(value) == "Instance" then
+ newParent[key] = `Instance({value:GetFullName()})`
+ elseif typeof(value) == "BrickColor" then
+ newParent[key] = `BrickColor({value.Name})`
+ elseif typeof(value) == "NumberRange" then
+ newParent[key] = `NumberRange({value.Min}, {value.Max})`
+ elseif typeof(value) == "NumberSequence" then
+ local keypoints = {}
+ for _, kp in value.Keypoints do
+ table.insert(
+ keypoints,
+ `NumberSequenceKeypoint({kp.Time}, {kp.Value}, {kp.Envelope})`
+ )
+ end
+ newParent[key] = `NumberSequence({table.concat(keypoints, ", ")})`
+ elseif typeof(value) == "ColorSequence" then
+ local keypoints = {}
+ for _, kp in ipairs(value.Keypoints) do
+ table.insert(
+ keypoints,
+ `ColorSequenceKeypoint({kp.Time}, {kp.Value.R}, {kp.Value.G}, {kp.Value.B})`
+ )
+ end
+ newParent[key] = `ColorSequence({table.concat(keypoints, ", ")})`
+ elseif typeof(value) == "PhysicalProperties" then
+ newParent[key] =
+ `PhysicalProperties({value.Density}, {value.Friction}, {value.Elasticity}, {value.FrictionWeight}, {value.ElasticityWeight})`
+ elseif typeof(value) == "Vector2int16" then
+ newParent[key] = `Vector2int16({value.X}, {value.Y})`
+ elseif typeof(value) == "Vector3int16" then
+ newParent[key] = `Vector3int16({value.X}, {value.Y}, {value.Z})`
+ elseif typeof(value) == "PathWaypoint" then
+ newParent[key] =
+ `PathWaypoint({value.Position.X}, {value.Position.Y}, {value.Position.Z}, {value.Action})`
+ elseif typeof(value) == "OverlapParams" then
+ newParent[key] =
+ `OverlapParams({value.FilterDescendantsInstances}, {value.FilterType}, {value.MaxParts}, {value.CollisionGroup})`
+ elseif typeof(value) == "RaycastParams" then
+ newParent[key] =
+ `RaycastParams({value.FilterDescendantsInstances}, {value.FilterType}, {value.IgnoreWater}, {value.CollisionGroup})`
+ else
+ local canSerialize = pcall(HttpService.JSONEncode, HttpService, { value })
+ if canSerialize then
+ newParent[key] = value
+ else
+ warn(
+ `[React DevTools] Could not serialize value for key "{key}" (type '{typeof(
+ value
+ )}'):`,
+ value
+ )
+ newParent[key] = ENCODE_TO_NULL
+ end
+ end
+ end
+ return newParent
+ end
+
+ return visitPropsRecursive(tbl)
+end
+
+return serializeTable
diff --git a/modules/react-devtools-extensions/.robloxrc b/modules/react-devtools-extensions/.robloxrc
new file mode 100644
index 00000000..8f779576
--- /dev/null
+++ b/modules/react-devtools-extensions/.robloxrc
@@ -0,0 +1,8 @@
+{
+ "language": {
+ "mode": "strict"
+ },
+ "lint": {
+ "*": "enabled"
+ }
+}
\ No newline at end of file
diff --git a/modules/react-devtools-extensions/default.project.json b/modules/react-devtools-extensions/default.project.json
new file mode 100644
index 00000000..01014997
--- /dev/null
+++ b/modules/react-devtools-extensions/default.project.json
@@ -0,0 +1,6 @@
+{
+ "name": "ReactDevtoolsExtensions",
+ "tree": {
+ "$path": "src"
+ }
+}
diff --git a/modules/react-devtools-extensions/rotriever.toml b/modules/react-devtools-extensions/rotriever.toml
new file mode 100644
index 00000000..cc1c18b9
--- /dev/null
+++ b/modules/react-devtools-extensions/rotriever.toml
@@ -0,0 +1,22 @@
+[package]
+name = "ReactDevtoolsExtensions"
+version = { workspace = true }
+authors = { workspace = true }
+publish = true
+content_root = "src"
+files = ["*", "!**/__tests__/**"]
+
+[dependencies]
+LuauPolyfill = { workspace = true }
+Shared = { path = "../shared" }
+React = { path = "../react" }
+ReactDevtoolsShared = { path = "../react-devtools-shared" }
+ReactGlobals = { path = "../react-globals" }
+ReactRoblox = { path = "../react-roblox" }
+
+[dev_dependencies]
+JestGlobals = { workspace = true }
+# FIXME: Version 0.2.4 breaks the test "can connect to a Roact tree and inspect
+# its children and child branch nodes" in devtools-integration.roblox.spec.lua
+DeveloperTools = "github.com/Roblox/developer-tools@=0.2.3"
+ReactTestRenderer = { path = "../react-test-renderer" }
diff --git a/modules/react-devtools-extensions/src/__tests__/devtools-integration.roblox.spec.lua b/modules/react-devtools-extensions/src/__tests__/devtools-integration.roblox.spec.lua
new file mode 100644
index 00000000..71865200
--- /dev/null
+++ b/modules/react-devtools-extensions/src/__tests__/devtools-integration.roblox.spec.lua
@@ -0,0 +1,122 @@
+--[[
+ This test is currently run manually to verify the DeveloperTools library integrates into roact-alignment
+]]
+type Function = (...any) -> any?
+
+local Packages = script.Parent.Parent.Parent
+local ReactGlobals = require(Packages.ReactGlobals)
+local JestGlobals = require(Packages.Dev.JestGlobals)
+local jestExpect = JestGlobals.expect
+local jest = JestGlobals.jest
+local describe = JestGlobals.describe
+local beforeEach = JestGlobals.beforeEach
+local afterEach = JestGlobals.afterEach
+local it = JestGlobals.it
+local xit = JestGlobals.xit
+
+describe("Devtools Integration", function()
+ local inspector
+ local ReactDevtoolsExtensions
+ local React
+
+ beforeEach(function()
+ React = require(Packages.React)
+ jest.resetModules()
+ jest.useFakeTimers()
+ local DeveloperTools = require(Packages.Dev.DeveloperTools)
+ ReactDevtoolsExtensions = require(Packages.ReactDevtoolsExtensions)
+
+ inspector = DeveloperTools.forLibrary("UniversalApp", {})
+ inspector:initRoact(ReactDevtoolsExtensions)
+ end)
+
+ afterEach(function()
+ jest.resetModules()
+ jest.useRealTimers()
+ ReactGlobals.__REACT_DEVTOOLS_GLOBAL_HOOK__ = nil
+ end)
+
+ -- Devtools naturally relies on using DEV mode, so this test only makes
+ -- sense when it's enabled
+ local itSkipIfNonDEV = it
+ if not ReactGlobals.__DEV__ then
+ itSkipIfNonDEV = xit :: any
+ end
+
+ -- ROBLOX FIXME: This test doesn't seem to work when focused
+ itSkipIfNonDEV(
+ "can connect to a Roact tree and inspect its children and child branch nodes",
+ function()
+ local ReactRoblox = require(Packages.ReactRoblox)
+
+ local function act(callback: Function): ()
+ local actTestRenderer = require(Packages.Dev.ReactTestRenderer).act
+
+ actTestRenderer(function()
+ callback()
+ end)
+
+ while jest.getTimerCount() > 0 do
+ actTestRenderer(function()
+ jest.runAllTimers()
+ end)
+ end
+ end
+
+ -- Create a Roact root
+ act(function()
+ local root = ReactRoblox.createRoot(Instance.new("Frame"))
+ return root:render(React.createElement("TextLabel", { Text = "Test" }))
+ end)
+
+ -- Deliver new root to the store
+ local devtools = inspector.workers.reactTargetWatcher.devtools
+ devtools.bridge:_flush()
+
+ -- Check that the target has been added
+ local _, target = next(inspector.targets)
+
+ jestExpect(target.name).toBe("#1")
+
+ -- Attach to the tree
+ target.listener.onEvent("TEST")
+
+ local worker = inspector.workers[target.id]
+
+ -- Stub the message handler
+ local spy = jest.fn()
+ worker.send = spy
+ worker:showChildren({})
+
+ jestExpect(spy).toBeCalledWith(worker, {
+ eventName = "RoactInspector.ShowChildren",
+ path = {},
+ children = {
+ ["1.2"] = {
+ Children = {},
+ Icon = "Branch",
+ Name = "TextLabel",
+ Path = { "1.2" },
+ },
+ },
+ })
+
+ worker:showBranch({ "1.2" })
+
+ jestExpect(spy.mock.calls[2][2].eventName).toBe("RoactInspector.ShowBranch")
+ jestExpect(spy.mock.calls[2][2].branch).toEqual({
+ {
+ Icon = "Branch",
+ Name = "Root",
+ Link = "",
+ Source = "",
+ },
+ {
+ Name = "TextLabel",
+ Link = "",
+ Source = "",
+ } :: any, -- TODO Luau: Allow elements of an array to have different types.
+ })
+ end
+ )
+end)
diff --git a/modules/react-devtools-extensions/src/backend.lua b/modules/react-devtools-extensions/src/backend.lua
new file mode 100644
index 00000000..a1961d27
--- /dev/null
+++ b/modules/react-devtools-extensions/src/backend.lua
@@ -0,0 +1,148 @@
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.2/packages/react-devtools-extensions/src/backend.js
+local Packages = script.Parent.Parent
+local ReactGlobals = require(Packages.ReactGlobals)
+local LuauPolyfill = require(Packages.LuauPolyfill)
+-- ROBLOX deviation START: not needed
+-- local Boolean = LuauPolyfill.Boolean
+-- ROBLOX deviation END
+type Array = LuauPolyfill.Array
+-- Do not use imports or top-level requires here!
+-- Running module factories is intentionally delayed until we know the hook exists.
+-- This is to avoid issues like: https://github.com/facebook/react-devtools/issues/1039
+--[[* @flow ]]
+
+-- ROBLOX deviation START: not needed
+-- local function welcome(event)
+-- if event.source ~= window or event.data.source ~= "react-devtools-content-script" then
+-- return
+-- end
+-- window:removeEventListener("message", welcome)
+-- setup(window.__REACT_DEVTOOLS_GLOBAL_HOOK__)
+-- end
+-- window:addEventListener("message", welcome)
+-- ROBLOX deviation END
+-- ROBLOX deviation START: add types for BridgeListener
+type BridgeEvent = {
+ source: string,
+ event: any,
+ payload: any,
+}
+type BridgeListener = (BridgeEvent) -> ()
+-- ROBLOX deviation END
+local function setup(hook)
+ -- ROBLOX deviation START: add bridgeListeners instead of listening to messages
+ local bridgeListeners: Array = {}
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: not needed
+ -- if
+ -- hook == nil --[[ ROBLOX CHECK: loose equality used upstream ]]
+ -- then
+ -- -- DevTools didn't get injected into this page (maybe b'c of the contentType).
+ -- return
+ -- end
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: fix imports
+ -- local Agent = require_("react-devtools-shared/src/backend/agent").default
+ -- local Bridge = require_("react-devtools-shared/src/bridge").default
+ -- local initBackend = require_("react-devtools-shared/src/backend").initBackend
+ local ReactDevtoolsShared = require(Packages.ReactDevtoolsShared)
+ local backend = ReactDevtoolsShared.backend
+ local Agent = backend.agent
+ local Bridge = ReactDevtoolsShared.bridge
+ local initBackend = backend.initBackend
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: not needed
+ -- local setupNativeStyleEditor = require_(
+ -- "react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor"
+ -- ).default
+ -- ROBLOX deviation END
+ local bridge = Bridge.new({
+ -- ROBLOX deviation START: no self param needed
+ -- listen = function(self, fn)
+ listen = function(fn: BridgeListener)
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: Because we don't listen for all messages on window like in browser, we don't need to verify their source
+ -- local function listener(event)
+ -- if
+ -- event.source ~= window
+ -- or not Boolean.toJSBoolean(event.data)
+ -- or event.data.source ~= "react-devtools-content-script"
+ -- or not Boolean.toJSBoolean(event.data.payload)
+ -- then
+ -- return
+ -- end
+ -- fn(event.data.payload)
+ -- end
+ -- window:addEventListener("message", listener)
+ -- return function()
+ -- window:removeEventListener("message", listener)
+ -- end
+ table.insert(bridgeListeners, fn)
+ return function()
+ for index, value in bridgeListeners do
+ if value == fn then
+ table.remove(bridgeListeners, index)
+ return
+ end
+ end
+ end
+ -- ROBLOX deviation END
+ end,
+ -- ROBLOX deviation START: no self param needed
+ -- send = function(self, event: string, payload: any, transferable: Array?)
+ send = function(event: string, payload: any, transferable: Array?)
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: custom implementation
+ -- window:postMessage({
+ -- source = "react-devtools-bridge",
+ -- payload = { event = event, payload = payload },
+ -- }, "*", transferable)
+ for _, fn in bridgeListeners do
+ fn({
+ source = "react-devtools-bridge",
+ event = event,
+ payload = payload,
+ })
+ end
+ -- ROBLOX deviation END
+ end,
+ })
+ local agent = Agent.new(bridge)
+ agent:addListener("shutdown", function()
+ -- If we received 'shutdown' from `agent`, we assume the `bridge` is already shutting down,
+ -- and that caused the 'shutdown' event on the `agent`, so we don't need to call `bridge.shutdown()` here.
+ -- ROBLOX deviation START: use dot notation
+ -- hook:emit("shutdown")
+ hook.emit("shutdown")
+ -- ROBLOX deviation END
+ end)
+ -- ROBLOX deviation START: use _G instead of window
+ -- initBackend(hook, agent, window) -- Let the frontend know that the backend has attached listeners and is ready for messages.
+ initBackend(hook, agent, ReactGlobals)
+ -- ROBLOX deviation END
+ -- This covers the case of syncing saved values after reloading/navigating while DevTools remain open.
+ bridge:send("extensionBackendInitialized") -- Setup React Native style editor if a renderer like react-native-web has injected it.
+ -- ROBLOX deviation START: not needed
+ -- if Boolean.toJSBoolean(hook.resolveRNStyle) then
+ -- setupNativeStyleEditor(
+ -- bridge,
+ -- agent,
+ -- hook.resolveRNStyle,
+ -- hook.nativeStyleEditorValidAttributes
+ -- )
+ -- end
+ -- ROBLOX deviation END
+ -- ROBLOX deviation START: add return
+ return {
+ agent = agent,
+ hook = hook,
+ bridge = bridge,
+ }
+ -- ROBLOX deviation END
+end
+
+-- ROBLOX deviation START: add return
+return {
+ setup = setup,
+}
+-- ROBLOX deviation END
diff --git a/modules/react-devtools-extensions/src/init.lua b/modules/react-devtools-extensions/src/init.lua
new file mode 100644
index 00000000..467cec5e
--- /dev/null
+++ b/modules/react-devtools-extensions/src/init.lua
@@ -0,0 +1,53 @@
+-- ROBLOX note: no upstream
+-- ROBLOX note: The setup function adds the glue required for DeveloperTools to initialize the Roact devtools correctly
+local Packages = script.Parent
+
+return {
+ setup = function(debugMode: boolean)
+ local ReactGlobals = require(Packages.ReactGlobals)
+
+ -- ROBLOX note: Set globals for React devtools to work
+ ReactGlobals.__DEV__ = true
+ ReactGlobals.__DEBUG__ = debugMode or false
+ ReactGlobals.__PROFILE__ = true
+ ReactGlobals.__EXPERIMENTAL__ = true
+ -- ROBLOX note: Don't hide host coomponents as the current Developer Inspector uses on these to preserve a
+ -- direct mapping between the Inspector tree and the Explorer tree as requested by design.
+ ReactGlobals.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = {}
+
+ local ReactDevtoolsShared = require(Packages.ReactDevtoolsShared)
+ local setup = require(Packages.ReactDevtoolsExtensions.backend).setup
+ local installHook = ReactDevtoolsShared.hook.installHook
+ local Store = ReactDevtoolsShared.devtools.store
+
+ -- ROBLOX note: Ensure that the global hook is installed before the injection into DevTools
+ installHook(ReactGlobals)
+
+ -- ROBLOX note: Ensure that ReactRoblox is loaded after injection so that the ReactHostConfig is populated correctly
+ require(Packages.React)
+ require(Packages.ReactRoblox)
+
+ local hook = ReactGlobals.__REACT_DEVTOOLS_GLOBAL_HOOK__
+
+ -- ROBLOX note: Make sure that this method was called before ReactRoblox was first required,
+ -- otherwise the profiler will not be enabled for the session.
+ local ReactFeatureFlags = require(Packages.Shared).ReactFeatureFlags
+ if not ReactFeatureFlags.enableSchedulingProfiler then
+ warn(
+ "[DeveloperTools] React was initialized before DeveloperTools. Call inspector.setupReactDevtools before requiring React to enable profiling."
+ )
+ end
+
+ local result = setup(hook)
+
+ -- ROBLOX note: The DeveloperTools library is only passed the ReactDevtoolsExtensions API to keep the
+ -- devtools init process compact for users. Initialize the store so DeveloperTools doesn't also need to be
+ -- passed the ReactDevtoolsShared API.
+ return {
+ agent = result.agent,
+ bridge = result.bridge,
+ hook = result.hook,
+ store = Store.new(result.bridge),
+ }
+ end,
+}
diff --git a/modules/react-devtools-shared/.robloxrc b/modules/react-devtools-shared/.robloxrc
new file mode 100644
index 00000000..8f779576
--- /dev/null
+++ b/modules/react-devtools-shared/.robloxrc
@@ -0,0 +1,8 @@
+{
+ "language": {
+ "mode": "strict"
+ },
+ "lint": {
+ "*": "enabled"
+ }
+}
\ No newline at end of file
diff --git a/modules/react-devtools-shared/default.project.json b/modules/react-devtools-shared/default.project.json
new file mode 100644
index 00000000..6be6fdad
--- /dev/null
+++ b/modules/react-devtools-shared/default.project.json
@@ -0,0 +1,6 @@
+{
+ "name": "ReactDevtoolsShared",
+ "tree": {
+ "$path": "src"
+ }
+}
diff --git a/modules/react-devtools-shared/rotriever.toml b/modules/react-devtools-shared/rotriever.toml
new file mode 100644
index 00000000..ebde2c1e
--- /dev/null
+++ b/modules/react-devtools-shared/rotriever.toml
@@ -0,0 +1,23 @@
+[package]
+name = "ReactDevtoolsShared"
+version = { workspace = true }
+authors = { workspace = true }
+publish = true
+content_root = "src"
+files = ["*", "!**/__tests__/**"]
+
+[dependencies]
+LuauPolyfill = { workspace = true }
+Shared = { path = "../shared" }
+React = { path = "../react" }
+ReactReconciler = { path = "../react-reconciler" }
+ReactRoblox = { path = "../react-roblox" }
+ReactIs = { path = "../react-is" }
+ReactDebugTools = { path = "../react-debug-tools" }
+ReactGlobals = { path = "../react-globals" }
+
+[dev_dependencies]
+JestGlobals = { workspace = true }
+Scheduler = { path = "../scheduler" }
+Promise = { workspace = true }
+ReactTestRenderer = { path = "../react-test-renderer" }
diff --git a/modules/react-devtools-shared/src/__tests__/__snapshots__/storeComponentFilters.spec.snap.lua b/modules/react-devtools-shared/src/__tests__/__snapshots__/storeComponentFilters.spec.snap.lua
new file mode 100644
index 00000000..f403f1b7
--- /dev/null
+++ b/modules/react-devtools-shared/src/__tests__/__snapshots__/storeComponentFilters.spec.snap.lua
@@ -0,0 +1,143 @@
+-- Jest Roblox Snapshot v1, http://roblox.github.io/jest-roblox-internal/snapshot-testing
+local exports = {}
+exports[ [=[Store component filters should filter HOCs: 1: mount 1]=] ] = [=[
+
+"[root]
+ ▾ [Bar][Foo]
+ ▾ [Foo]
+ ▾
+ "
+]=]
+
+exports[ [=[Store component filters should filter HOCs: 2: hide all HOCs 1]=] ] = [=[
+
+"[root]
+ ▾
+ "
+]=]
+
+exports[ [=[Store component filters should filter HOCs: 3: disable HOC filter 1]=] ] = [=[
+
+"[root]
+ ▾ [Bar][Foo]
+ ▾ [Foo]
+ ▾
+ "
+]=]
+
+exports[ [=[Store component filters should filter by display name: 1: mount 1]=] ] = [=[
+
+"[root]
+ ▾
+
+ ▾
+
+ ▾
+ "
+]=]
+
+exports[ [=[Store component filters should filter by display name: 2: filter "Foo" 1]=] ] = [=[
+
+"[root]
+
+ ▾
+
+ ▾
+ "
+]=]
+
+exports[ [=[Store component filters should filter by display name: 3: filter "Ba" 1]=] ] = [=[
+
+"[root]
+ ▾
+
+
+ "
+]=]
+
+exports[ [=[Store component filters should filter by display name: 4: filter "B.z" 1]=] ] = [=[
+
+"[root]
+ ▾
+
+ ▾
+
+ "
+]=]
+
+exports[ [=[Store component filters should filter by path: 1: mount 1]=] ] = [=[
+
+"[root]
+ ▾
+ "
+]=]
+
+exports[ [=[Store component filters should filter by path: 2: hide all components declared within this test filed 1]=] ] = [=[
+
+"[root]
+ ▾
+ "
+]=]
+
+exports[ [=[Store component filters should filter by path: 3: hide components in a made up fake path 1]=] ] = [=[
+
+"[root]
+ ▾
+ "
+]=]
+
+exports[ [=[Store component filters should ignore invalid ElementTypeRoot filter: 1: mount 1]=] ] = [=[
+
+"[root]
+ ▾
+ "
+]=]
+
+exports[ [=[Store component filters should ignore invalid ElementTypeRoot filter: 2: add invalid filter 1]=] ] = [=[
+
+"[root]
+ ▾
+ "
+]=]
+
+exports[ [=[Store component filters should support filtering by element type: 1: mount 1]=] ] = [=[
+
+"[root]
+ ▾
+ ▾
+ ▾
+ "
+]=]
+
+exports[ [=[Store component filters should support filtering by element type: 2: hide host components 1]=] ] = [=[
+
+"[root]
+ ▾
+ "
+]=]
+
+exports[ [=[Store component filters should support filtering by element type: 3: hide class components 1]=] ] = [=[
+
+"[root]
+ ▾
+ ▾
+ "
+]=]
+
+exports[ [=[Store component filters should support filtering by element type: 4: hide class and function components 1]=] ] = [=[
+
+"[root]
+ ▾
+ "
+]=]
+
+exports[ [=[Store component filters should support filtering by element type: 5: disable all filters 1]=] ] = [=[
+
+"[root]
+ ▾
+ ▾
+ ▾
+ "
+]=]
+
+return exports
diff --git a/modules/react-devtools-shared/src/__tests__/bridge.spec.lua b/modules/react-devtools-shared/src/__tests__/bridge.spec.lua
new file mode 100644
index 00000000..9f074ebd
--- /dev/null
+++ b/modules/react-devtools-shared/src/__tests__/bridge.spec.lua
@@ -0,0 +1,62 @@
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.1/packages/react-devtools-shared/src/__tests__/bridge-test.js
+--[[*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+]]
+local Packages = script.Parent.Parent.Parent
+local JestGlobals = require(Packages.Dev.JestGlobals)
+local describe = JestGlobals.describe
+local it = JestGlobals.it
+local beforeEach = JestGlobals.beforeEach
+local jestExpect = JestGlobals.expect
+local jest = JestGlobals.jest
+
+describe("bridge", function()
+ local Bridge
+
+ beforeEach(function()
+ jest.resetModules()
+ jest.useFakeTimers()
+ Bridge = require(script.Parent.Parent.bridge)
+ end)
+
+ it("should shutdown properly", function()
+ local wall = {
+ listen = jest.fn(function()
+ return function() end
+ end),
+ send = jest.fn(),
+ }
+ local bridge = Bridge.new(wall)
+
+ -- Check that we're wired up correctly.
+ bridge:send("reloadAppForProfiling")
+ jest.runAllTimers()
+ jestExpect(wall.send).toHaveBeenCalledWith("reloadAppForProfiling")
+
+ -- Should flush pending messages and then shut down.
+ wall.send.mockClear()
+ bridge:send("update", "1")
+ bridge:send("update", "2")
+ bridge:shutdown()
+ jest.runAllTimers()
+ jestExpect(wall.send).toHaveBeenCalledWith("update", "1")
+ jestExpect(wall.send).toHaveBeenCalledWith("update", "2")
+ jestExpect(wall.send).toHaveBeenCalledWith("shutdown")
+
+ -- Verify that the Bridge doesn't send messages after shutdown.
+
+ wall.send.mockClear()
+ -- ROBLOX deviation: instead of spying on console, use toWarnDev matcher
+ jestExpect(function()
+ bridge:send("should not send")
+ end).toWarnDev(
+ 'Cannot send message "should not send" through a Bridge that has been shutdown.',
+ { withoutStack = true }
+ )
+ jest.runAllTimers()
+ jestExpect(wall.send).never.toHaveBeenCalled()
+ end)
+end)
diff --git a/modules/react-devtools-shared/src/__tests__/console.spec.lua b/modules/react-devtools-shared/src/__tests__/console.spec.lua
new file mode 100644
index 00000000..a85b8e88
--- /dev/null
+++ b/modules/react-devtools-shared/src/__tests__/console.spec.lua
@@ -0,0 +1,505 @@
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.1/packages/react-devtools-shared/src/__tests__/console-test.js
+--[[*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+]]
+local Packages = script.Parent.Parent.Parent
+local ReactGlobals = require(Packages.ReactGlobals)
+local JestGlobals = require(Packages.Dev.JestGlobals)
+local describe = JestGlobals.describe
+local xit = JestGlobals.xit
+local beforeEach = JestGlobals.beforeEach
+local jestExpect = JestGlobals.expect
+local jest = JestGlobals.jest
+
+-- ROBLOX deviation: Stub for now
+local Console = {
+ patch = function(...)
+ print("Console.patch", ...)
+ end,
+ unpatch = function(...)
+ print("Console.unpatch", ...)
+ end,
+ dangerous_setTargetConsoleForTesting = function(...) end,
+ registerRenderer = function(...) end,
+}
+
+local React
+local ReactRoblox
+local utils = require(script.Parent.utils)
+
+describe("console", function()
+ local act
+ local fakeConsole
+ local mockError
+ local mockInfo
+ local mockLog
+ local mockWarn
+ local patchConsole
+ local unpatchConsole
+
+ beforeEach(function()
+ jest.resetModules()
+
+ patchConsole = Console.patch
+ unpatchConsole = Console.unpatch
+
+ -- Patch a fake console so we can verify with tests below.
+ -- Patching the real console is too complicated,
+ -- because Jest itself has hooks into it as does our test env setup.
+ mockError = jest.fn()
+ mockInfo = jest.fn()
+ mockLog = jest.fn()
+ mockWarn = jest.fn()
+ fakeConsole = {
+ error = mockError,
+ info = mockInfo,
+ log = mockLog,
+ warn = mockWarn,
+ }
+
+ Console.dangerous_setTargetConsoleForTesting(fakeConsole)
+
+ -- Note the Console module only patches once,
+ -- so it's important to patch the test console before injection.
+ patchConsole({
+ appendComponentStack = true,
+ breakOnWarn = false,
+ })
+
+ local inject = ReactGlobals.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject;
+ (ReactGlobals.__REACT_DEVTOOLS_GLOBAL_HOOK__ :: any).inject = function(internals)
+ inject(internals)
+
+ Console.registerRenderer(internals)
+ end
+
+ React = require(Packages.React)
+ ReactRoblox = require(Packages.ReactRoblox)
+
+ act = utils.act
+ end)
+
+ local function normalizeCodeLocInfo(str)
+ -- ROBLOX deviation: Lua stack traces won't match JS ones
+ -- ROBLOX TODO: verify this
+ return str
+ end
+
+ xit(
+ "should not patch console methods that do not receive component stacks",
+ function()
+ jestExpect(fakeConsole.error).never.toBe(mockError)
+ jestExpect(fakeConsole.info).toBe(mockInfo)
+ jestExpect(fakeConsole.log).toBe(mockLog)
+ jestExpect(fakeConsole.warn).never.toBe(mockWarn)
+ end
+ )
+
+ xit("should only patch the console once", function()
+ local prevError = fakeConsole.error
+ local prevWarn = fakeConsole.warn
+
+ patchConsole({
+ appendComponentStack = true,
+ breakOnWarn = false,
+ })
+ jestExpect(fakeConsole.error).toBe(prevError)
+ jestExpect(fakeConsole.warn).toBe(prevWarn)
+ end)
+
+ xit("should un-patch when requested", function()
+ jestExpect(fakeConsole.error).never.toBe(mockError)
+ jestExpect(fakeConsole.warn).never.toBe(mockWarn)
+ unpatchConsole()
+ jestExpect(fakeConsole.error).toBe(mockError)
+ jestExpect(fakeConsole.warn).toBe(mockWarn)
+ end)
+
+ xit("should pass through logs when there is no current fiber", function()
+ jestExpect(mockLog).toHaveBeenCalledTimes(0)
+ jestExpect(mockWarn).toHaveBeenCalledTimes(0)
+ jestExpect(mockError).toHaveBeenCalledTimes(0)
+ fakeConsole.log("log")
+ fakeConsole.warn("warn")
+ fakeConsole.error("error")
+ jestExpect(mockLog).toHaveBeenCalledTimes(1)
+ jestExpect(mockLog.mock.calls[0]).toHaveLength(1)
+ jestExpect(mockLog.mock.calls[0][0]).toBe("log")
+ jestExpect(mockWarn).toHaveBeenCalledTimes(1)
+ jestExpect(mockWarn.mock.calls[0]).toHaveLength(1)
+ jestExpect(mockWarn.mock.calls[0][0]).toBe("warn")
+ jestExpect(mockError).toHaveBeenCalledTimes(1)
+ jestExpect(mockError.mock.calls[0]).toHaveLength(1)
+ jestExpect(mockError.mock.calls[0][0]).toBe("error")
+ end)
+
+ xit("should not append multiple stacks", function()
+ local Child = function()
+ fakeConsole.warn("warn\n in Child (at fake.js:123)")
+ fakeConsole.error("error", "\n in Child (at fake.js:123)")
+ return nil
+ end
+
+ -- ROBLOX deviation: Use createRoot instead of DOM element
+ local root = ReactRoblox.createRoot(Instance.new("Frame"))
+
+ act(function()
+ return root:render(React.createElement(Child))
+ end)
+ jestExpect(mockWarn).toHaveBeenCalledTimes(1)
+ jestExpect(mockWarn.mock.calls[0]).toHaveLength(1)
+ -- ROBLOX TODO: What is printed instead of this?
+ jestExpect(mockWarn.mock.calls[0][0]).toBe("warn\n in Child (at fake.js:123)")
+ jestExpect(mockError).toHaveBeenCalledTimes(1)
+ jestExpect(mockError.mock.calls[0]).toHaveLength(2)
+ jestExpect(mockError.mock.calls[0][0]).toBe("error")
+ jestExpect(mockError.mock.calls[0][1]).toBe("\n in Child (at fake.js:123)")
+ end)
+
+ xit(
+ "should append component stacks to errors and warnings logged during render",
+ function()
+ local Intermediate = function(_ref2)
+ local children = _ref2.children
+ return children
+ end
+ -- ROBLOX deviation: switched ordering for variable definition order
+ local Child = function(_ref4)
+ local _children = _ref4.children
+
+ fakeConsole.error("error")
+ fakeConsole.log("log")
+ fakeConsole.warn("warn")
+
+ return nil
+ end
+ local Parent = function(_ref3)
+ local _children = _ref3.children
+ return React.createElement(
+ Intermediate,
+ nil,
+ React.createElement(Child, nil)
+ )
+ end
+
+ -- ROBLOX deviation: Use createRoot instead of DOM element
+ local root = ReactRoblox.createRoot(Instance.new("Frame"))
+
+ act(function()
+ return root:render(React.createElement(Parent, nil))
+ end)
+ jestExpect(mockLog).toHaveBeenCalledTimes(1)
+ jestExpect(mockLog.mock.calls[0]).toHaveLength(1)
+ jestExpect(mockLog.mock.calls[0][0]).toBe("log")
+ jestExpect(mockWarn).toHaveBeenCalledTimes(1)
+ jestExpect(mockWarn.mock.calls[0]).toHaveLength(2)
+ jestExpect(mockWarn.mock.calls[0][0]).toBe("warn")
+ -- ROBLOX TODO: What is printed instead of this?
+ jestExpect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
+ "\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)"
+ )
+ jestExpect(mockError).toHaveBeenCalledTimes(1)
+ jestExpect(mockError.mock.calls[0]).toHaveLength(2)
+ jestExpect(mockError.mock.calls[0][0]).toBe("error")
+ jestExpect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
+ "\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)"
+ )
+ end
+ )
+ xit(
+ "should append component stacks to errors and warnings logged from effects",
+ function()
+ local Intermediate = function(_ref5)
+ local children = _ref5.children
+ return children
+ end
+ -- ROBLOX deviation: switched ordering for variable definition order
+ local Child = function(_ref7)
+ local _children = _ref7.children
+
+ React.useLayoutEffect(function()
+ fakeConsole.error("active error")
+ fakeConsole.log("active log")
+ fakeConsole.warn("active warn")
+ end)
+ React.useEffect(function()
+ fakeConsole.error("passive error")
+ fakeConsole.log("passive log")
+ fakeConsole.warn("passive warn")
+ end)
+
+ return nil
+ end
+ local Parent = function(_ref6)
+ local _children = _ref6.children
+
+ return React.createElement(
+ Intermediate,
+ nil,
+ React.createElement(Child, nil)
+ )
+ end
+
+ -- ROBLOX deviation: Use createRoot instead of DOM element
+ local root = ReactRoblox.createRoot(Instance.new("Frame"))
+
+ act(function()
+ return root:render(React.createElement(Parent, nil))
+ end)
+ jestExpect(mockLog).toHaveBeenCalledTimes(2)
+ jestExpect(mockLog.mock.calls[0]).toHaveLength(1)
+ jestExpect(mockLog.mock.calls[0][0]).toBe("active log")
+ jestExpect(mockLog.mock.calls[1]).toHaveLength(1)
+ jestExpect(mockLog.mock.calls[1][0]).toBe("passive log")
+ jestExpect(mockWarn).toHaveBeenCalledTimes(2)
+ jestExpect(mockWarn.mock.calls[0]).toHaveLength(2)
+ jestExpect(mockWarn.mock.calls[0][0]).toBe("active warn")
+ -- ROBLOX TODO: What is printed instead of this?
+ jestExpect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
+ "\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)"
+ )
+ jestExpect(mockWarn.mock.calls[1]).toHaveLength(2)
+ jestExpect(mockWarn.mock.calls[1][0]).toBe("passive warn")
+ jestExpect(normalizeCodeLocInfo(mockWarn.mock.calls[1][1])).toEqual(
+ "\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)"
+ )
+ jestExpect(mockError).toHaveBeenCalledTimes(2)
+ jestExpect(mockError.mock.calls[0]).toHaveLength(2)
+ jestExpect(mockError.mock.calls[0][0]).toBe("active error")
+ jestExpect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
+ "\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)"
+ )
+ jestExpect(mockError.mock.calls[1]).toHaveLength(2)
+ jestExpect(mockError.mock.calls[1][0]).toBe("passive error")
+ jestExpect(normalizeCodeLocInfo(mockError.mock.calls[1][1])).toBe(
+ "\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)"
+ )
+ end
+ )
+ xit(
+ "should append component stacks to errors and warnings logged from commit hooks",
+ function()
+ local Intermediate = function(_ref8)
+ local children = _ref8.children
+
+ return children
+ end
+ -- ROBLOX deviation: switched ordering for variable definition order
+ local Child = React.Component:extend("Child")
+ local Parent = function(_ref9)
+ local _children = _ref9.children
+
+ return React.createElement(
+ Intermediate,
+ nil,
+ React.createElement(Child, nil)
+ )
+ end
+
+ function Child:componentDidMount()
+ fakeConsole.error("didMount error")
+ fakeConsole.log("didMount log")
+ fakeConsole.warn("didMount warn")
+ end
+ function Child:componentDidUpdate()
+ fakeConsole.error("didUpdate error")
+ fakeConsole.log("didUpdate log")
+ fakeConsole.warn("didUpdate warn")
+ end
+ function Child:render()
+ return nil
+ end
+
+ -- ROBLOX deviation: Use createRoot instead of DOM element
+ local root = ReactRoblox.createRoot(Instance.new("Frame"))
+
+ act(function()
+ return root:render(React.createElement(Parent, nil))
+ end)
+ act(function()
+ return root:render(React.createElement(Parent, nil))
+ end)
+ jestExpect(mockLog).toHaveBeenCalledTimes(2)
+ jestExpect(mockLog.mock.calls[0]).toHaveLength(1)
+ jestExpect(mockLog.mock.calls[0][0]).toBe("didMount log")
+ jestExpect(mockLog.mock.calls[1]).toHaveLength(1)
+ jestExpect(mockLog.mock.calls[1][0]).toBe("didUpdate log")
+ jestExpect(mockWarn).toHaveBeenCalledTimes(2)
+ jestExpect(mockWarn.mock.calls[0]).toHaveLength(2)
+ jestExpect(mockWarn.mock.calls[0][0]).toBe("didMount warn")
+ -- ROBLOX TODO: What is printed instead of this?
+ jestExpect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
+ "\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)"
+ )
+ jestExpect(mockWarn.mock.calls[1]).toHaveLength(2)
+ jestExpect(mockWarn.mock.calls[1][0]).toBe("didUpdate warn")
+ jestExpect(normalizeCodeLocInfo(mockWarn.mock.calls[1][1])).toEqual(
+ "\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)"
+ )
+ jestExpect(mockError).toHaveBeenCalledTimes(2)
+ jestExpect(mockError.mock.calls[0]).toHaveLength(2)
+ jestExpect(mockError.mock.calls[0][0]).toBe("didMount error")
+ jestExpect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
+ "\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)"
+ )
+ jestExpect(mockError.mock.calls[1]).toHaveLength(2)
+ jestExpect(mockError.mock.calls[1][0]).toBe("didUpdate error")
+ jestExpect(normalizeCodeLocInfo(mockError.mock.calls[1][1])).toBe(
+ "\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)"
+ )
+ end
+ )
+ xit(
+ "should append component stacks to errors and warnings logged from gDSFP",
+ function()
+ local Intermediate = function(props)
+ local children = props.children
+ return children
+ end
+ -- ROBLOX deviation: switched ordering for variable definition order
+ local Child = React.Component:extend("Child")
+ local Parent = function(props)
+ local _children = props.children
+ return React.createElement(Intermediate, nil, React.createElement(Child))
+ end
+
+ function Child.getDerivedStateFromProps()
+ fakeConsole.error("error")
+ fakeConsole.log("log")
+ fakeConsole.warn("warn")
+ return nil
+ end
+ function Child:render()
+ return nil
+ end
+
+ -- ROBLOX deviation: Use createRoot instead of DOM element
+ local root = ReactRoblox.createRoot(Instance.new("Frame"))
+
+ act(function()
+ return root:render(React.createElement(Parent, nil))
+ end)
+ jestExpect(mockLog).toHaveBeenCalledTimes(1)
+ jestExpect(mockLog.mock.calls[0]).toHaveLength(1)
+ jestExpect(mockLog.mock.calls[0][0]).toBe("log")
+ jestExpect(mockWarn).toHaveBeenCalledTimes(1)
+ jestExpect(mockWarn.mock.calls[0]).toHaveLength(2)
+ jestExpect(mockWarn.mock.calls[0][0]).toBe("warn")
+ -- ROBLOX TODO: What is printed instead of this?
+ jestExpect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
+ "\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)"
+ )
+ jestExpect(mockError).toHaveBeenCalledTimes(1)
+ jestExpect(mockError.mock.calls[0]).toHaveLength(2)
+ jestExpect(mockError.mock.calls[0][0]).toBe("error")
+ jestExpect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
+ "\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)"
+ )
+ end
+ )
+ xit("should append stacks after being uninstalled and reinstalled", function()
+ local Child = function(_ref12)
+ local _children = _ref12.children
+
+ fakeConsole.warn("warn")
+ fakeConsole.error("error")
+
+ return nil
+ end
+
+ unpatchConsole()
+
+ -- ROBLOX deviation: Use createRoot instead of DOM element
+ local root = ReactRoblox.createRoot(Instance.new("Frame"))
+
+ act(function()
+ return root:render(React.createElement(Child, nil))
+ end)
+ jestExpect(mockWarn).toHaveBeenCalledTimes(1)
+ jestExpect(mockWarn.mock.calls[0]).toHaveLength(1)
+ jestExpect(mockWarn.mock.calls[0][0]).toBe("warn")
+ jestExpect(mockError).toHaveBeenCalledTimes(1)
+ jestExpect(mockError.mock.calls[0]).toHaveLength(1)
+ jestExpect(mockError.mock.calls[0][0]).toBe("error")
+ patchConsole({
+ appendComponentStack = true,
+ breakOnWarn = false,
+ })
+ act(function()
+ return root:render(React.createElement(Child, nil))
+ end)
+ jestExpect(mockWarn).toHaveBeenCalledTimes(2)
+ jestExpect(mockWarn.mock.calls[1]).toHaveLength(2)
+ jestExpect(mockWarn.mock.calls[1][0]).toBe("warn")
+ -- ROBLOX TODO: What is printed instead of this?
+ jestExpect(normalizeCodeLocInfo(mockWarn.mock.calls[1][1])).toEqual(
+ "\n in Child (at **)"
+ )
+ jestExpect(mockError).toHaveBeenCalledTimes(2)
+ jestExpect(mockError.mock.calls[1]).toHaveLength(2)
+ jestExpect(mockError.mock.calls[1][0]).toBe("error")
+ jestExpect(normalizeCodeLocInfo(mockError.mock.calls[1][1])).toBe(
+ "\n in Child (at **)"
+ )
+ end)
+ xit("should be resilient to prepareStackTrace", function()
+ -- ROBLOX TODO: what is a suitable alternative for this?
+ -- Error.prepareStackTrace = function(error, callsites)
+ -- local stack = {
+ -- 'An error occurred:',
+ -- error.message,
+ -- }
+
+ -- for i=0, callsites.length - 1 do
+ -- local callsite = callsites[i]
+
+ -- stack.push('\t' + callsite.getFunctionName(), '\t\tat ' + callsite.getFileName(), '\t\ton line ' + callsite.getLineNumber())
+ -- end
+
+ -- return stack.join('\n')
+ -- end
+
+ local Intermediate = function(_ref13)
+ local children = _ref13.children
+ return children
+ end
+ -- ROBLOX deviation: switched ordering for variable definition order
+ local Child = function(_ref15)
+ fakeConsole.error("error")
+ fakeConsole.log("log")
+ fakeConsole.warn("warn")
+
+ return nil
+ end
+ local Parent = function(_ref14)
+ local _children = _ref14.children
+ return React.createElement(Intermediate, nil, React.createElement(Child, nil))
+ end
+
+ -- ROBLOX deviation: Use createRoot instead of DOM element
+ local root = ReactRoblox.createRoot(Instance.new("Frame"))
+
+ act(function()
+ return root:render(React.createElement(Parent, nil))
+ end)
+ jestExpect(mockLog).toHaveBeenCalledTimes(1)
+ jestExpect(mockLog.mock.calls[0]).toHaveLength(1)
+ jestExpect(mockLog.mock.calls[0][0]).toBe("log")
+ jestExpect(mockWarn).toHaveBeenCalledTimes(1)
+ jestExpect(mockWarn.mock.calls[0]).toHaveLength(2)
+ jestExpect(mockWarn.mock.calls[0][0]).toBe("warn")
+ -- ROBLOX TODO: What is printed instead of this?
+ jestExpect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
+ "\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)"
+ )
+ jestExpect(mockError).toHaveBeenCalledTimes(1)
+ jestExpect(mockError.mock.calls[0]).toHaveLength(2)
+ jestExpect(mockError.mock.calls[0][0]).toBe("error")
+ jestExpect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
+ "\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)"
+ )
+ end)
+end)
diff --git a/modules/react-devtools-shared/src/__tests__/events.spec.lua b/modules/react-devtools-shared/src/__tests__/events.spec.lua
new file mode 100644
index 00000000..4b1155d4
--- /dev/null
+++ b/modules/react-devtools-shared/src/__tests__/events.spec.lua
@@ -0,0 +1,126 @@
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.1/packages/react-devtools-shared/src/__tests__/events-test.js
+-- /**
+-- * Copyright (c) Facebook, Inc. and its affiliates.
+-- *
+-- * This source code is licensed under the MIT license found in the
+-- *
+-- * LICENSE file in the root directory of this source tree.
+-- * @flow
+-- */
+
+local Packages = script.Parent.Parent.Parent
+local Error = require(Packages.LuauPolyfill).Error
+local JestGlobals = require(Packages.Dev.JestGlobals)
+local describe = JestGlobals.describe
+local it = JestGlobals.it
+local beforeEach = JestGlobals.beforeEach
+local jestExpect = JestGlobals.expect
+local jest = JestGlobals.jest
+
+describe("events", function()
+ local dispatcher
+ beforeEach(function()
+ jest.resetModules()
+ local EventEmitter = require(script.Parent.Parent.events)
+ dispatcher = EventEmitter.new()
+ end)
+
+ it("can dispatch an event with no listeners", function()
+ dispatcher:emit("event", 123)
+ end)
+
+ it("handles a listener being attached multiple times", function()
+ local callback = jest.fn()
+ dispatcher:addListener("event", callback)
+ dispatcher:addListener("event", callback)
+ dispatcher:emit("event", 123)
+ jestExpect(callback).toHaveBeenCalledTimes(1)
+ jestExpect(callback).toHaveBeenCalledWith(123)
+ end)
+
+ it("notifies all attached listeners of events", function()
+ local callback1 = jest.fn()
+ local callback2 = jest.fn()
+ local callback3 = jest.fn()
+ dispatcher:addListener("event", callback1)
+ dispatcher:addListener("event", callback2)
+ dispatcher:addListener("other-event", callback3)
+ dispatcher:emit("event", 123)
+ jestExpect(callback1).toHaveBeenCalledTimes(1)
+ jestExpect(callback1).toHaveBeenCalledWith(123)
+ jestExpect(callback2).toHaveBeenCalledTimes(1)
+ jestExpect(callback2).toHaveBeenCalledWith(123)
+ jestExpect(callback3).never.toHaveBeenCalled()
+ end)
+
+ it("calls later listeners before re-throwing if an earlier one throws", function()
+ local callbackThatThrows = jest.fn(function()
+ error(Error.new("expected"))
+ end)
+ local callback = jest.fn()
+ dispatcher:addListener("event", callbackThatThrows)
+ dispatcher:addListener("event", callback)
+ jestExpect(function()
+ dispatcher:emit("event", 123)
+ end).toThrow("expected")
+ jestExpect(callbackThatThrows).toHaveBeenCalledTimes(1)
+ jestExpect(callbackThatThrows).toHaveBeenCalledWith(123)
+ jestExpect(callback).toHaveBeenCalledTimes(1)
+ jestExpect(callback).toHaveBeenCalledWith(123)
+ end)
+
+ it("removes attached listeners", function()
+ local callback1 = jest.fn()
+ local callback2 = jest.fn()
+ dispatcher:addListener("event", callback1)
+ dispatcher:addListener("other-event", callback2)
+ dispatcher:removeListener("event", callback1)
+ dispatcher:emit("event", 123)
+ jestExpect(callback1).never.toHaveBeenCalled()
+ dispatcher:emit("other-event", 123)
+ jestExpect(callback2).toHaveBeenCalledTimes(1)
+ jestExpect(callback2).toHaveBeenCalledWith(123)
+ end)
+
+ it("removes all listeners", function()
+ local callback1 = jest.fn()
+ local callback2 = jest.fn()
+ local callback3 = jest.fn()
+ dispatcher:addListener("event", callback1)
+ dispatcher:addListener("event", callback2)
+ dispatcher:addListener("other-event", callback3)
+ dispatcher:removeAllListeners()
+ dispatcher:emit("event", 123)
+ dispatcher:emit("other-event", 123)
+ jestExpect(callback1).never.toHaveBeenCalled()
+ jestExpect(callback2).never.toHaveBeenCalled()
+ jestExpect(callback3).never.toHaveBeenCalled()
+ end)
+
+ it(
+ "should call the initial listeners even if others are added or removed during a dispatch",
+ function()
+ local callback2, callback3
+ local callback1 = jest.fn(function()
+ dispatcher:removeListener("event", callback2)
+ dispatcher:addListener("event", callback3)
+ end)
+ callback2 = jest.fn()
+ callback3 = jest.fn()
+ dispatcher:addListener("event", callback1)
+ dispatcher:addListener("event", callback2)
+ dispatcher:emit("event", 123)
+ jestExpect(callback1).toHaveBeenCalledTimes(1)
+ jestExpect(callback1).toHaveBeenCalledWith(123)
+ jestExpect(callback2).toHaveBeenCalledTimes(1)
+ jestExpect(callback2).toHaveBeenCalledWith(123)
+ jestExpect(callback3).never.toHaveBeenCalled()
+ dispatcher:emit("event", 456)
+ jestExpect(callback1).toHaveBeenCalledTimes(2)
+ jestExpect(callback1).toHaveBeenCalledWith(456)
+ jestExpect(callback2).toHaveBeenCalledTimes(1)
+ jestExpect(callback3).toHaveBeenCalledTimes(1)
+ jestExpect(callback3).toHaveBeenCalledWith(456)
+ end
+ )
+end)
diff --git a/modules/react-devtools-shared/src/__tests__/profilerStore.spec.lua b/modules/react-devtools-shared/src/__tests__/profilerStore.spec.lua
new file mode 100644
index 00000000..b51ddaab
--- /dev/null
+++ b/modules/react-devtools-shared/src/__tests__/profilerStore.spec.lua
@@ -0,0 +1,175 @@
+--!strict
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.1/packages/react-devtools-shared/src/__tests__/profilerStore-test.js
+--[[*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ ]]
+
+local Packages = script.Parent.Parent.Parent
+local ReactGlobals = require(Packages.ReactGlobals)
+local JestGlobals = require(Packages.Dev.JestGlobals)
+local jest = JestGlobals.jest
+local jestExpect = JestGlobals.expect
+local xdescribe = JestGlobals.xdescribe
+local it = JestGlobals.it
+local beforeEach = JestGlobals.beforeEach
+
+local devtoolsTypes = require(script.Parent.Parent.devtools.types)
+type Store = devtoolsTypes.Store
+
+local global = ReactGlobals :: any
+
+xdescribe("ProfilerStore", function()
+ local React
+ local ReactRoblox
+ local LuauPolyfill
+ local store: Store
+ local utils
+ local act
+
+ beforeEach(function()
+ utils = require(script.Parent.utils)
+ act = utils.act
+
+ store = global.store
+ store:setCollapseNodesByDefault(false)
+ store:setRecordChangeDescriptions(true)
+
+ React = require(Packages.React)
+ ReactRoblox = require(Packages.ReactRoblox)
+ LuauPolyfill = require(Packages.LuauPolyfill)
+
+ utils.beforeEachProfiling()
+ end)
+
+ it("should not remove profiling data when roots are unmounted", function()
+ local function Child()
+ return React.createElement("Frame")
+ end
+
+ local function Parent(props)
+ local count = props.count
+
+ local arr = table.create(count) :: any
+ for index = 1, count do
+ arr[index] = React.createElement(
+ Child,
+ { key = tostring(index), duration = index }
+ )
+ end
+ return arr
+ end
+
+ local containerA = ReactRoblox.createRoot(Instance.new("Frame"))
+ local containerB = ReactRoblox.createRoot(Instance.new("Frame"))
+ act(function()
+ containerA:render(React.createElement(Parent, { key = "A", count = 3 }))
+ containerB:render(React.createElement(Parent, { key = "B", count = 2 }))
+ end)
+ act(function()
+ store._profilerStore:startProfiling()
+ end)
+ act(function()
+ containerA:render(React.createElement(Parent, { key = "A", count = 4 }))
+ containerB:render(React.createElement(Parent, { key = "B", count = 1 }))
+ end)
+ act(function()
+ store._profilerStore:stopProfiling()
+ end)
+ local rootA = store:getRoots()[1]
+ local rootB = store:getRoots()[2]
+ act(function()
+ containerB:render(nil)
+ end)
+ jestExpect(store._profilerStore:getDataForRoot(rootA)).never.toBeNull()
+ act(function()
+ containerA:render(nil)
+ end)
+ jestExpect(store._profilerStore:getDataForRoot(rootB)).never.toBeNull()
+ end)
+ it(
+ "should not allow new/saved profiling data to be set while profiling is in progress",
+ function()
+ act(function()
+ return store._profilerStore:startProfiling()
+ end)
+ local fauxProfilingData = {
+ -- ROLBLOX deviation START: upstream doesn't typecheck, needs mandatory imported field
+ dataForRoots = LuauPolyfill.Map.new(),
+ imported = false,
+ -- ROBLOX deviation END
+ }
+
+ -- ROBLOX deviation: spyOn console.warn workaround
+ local mockWarn = jest.fn().mockName("console.warn")
+ LuauPolyfill.console.warn = mockWarn
+
+ store._profilerStore:profilingData(fauxProfilingData)
+ jestExpect(store._profilerStore.profilingData).never.toBe(fauxProfilingData)
+ jestExpect(mockWarn).toHaveBeenCalledTimes(1)
+ jestExpect(mockWarn).toHaveBeenCalledWith(
+ "Profiling data cannot be updated while profiling is in progress."
+ )
+
+ act(function()
+ return store._profilerStore:stopProfiling()
+ end)
+
+ store._profilerStore:profilingData(fauxProfilingData)
+ jestExpect(store._profilerStore:profilingData()).toBe(fauxProfilingData)
+ end
+ )
+
+ --[[ ROBLOX note: This seems to test edges that aren't present in our environment
+ -- This test covers current broken behavior (arguably) with the synthetic event system.
+ it("should filter empty commits", function()
+ local inputRef = React.createRef()
+ local function ControlledInput()
+ local name, setName = React.useState("foo")
+ local function handleChange(event)
+ return setName(event.target.value)
+ end
+ return React.createElement(
+ "input",
+ { ref = inputRef, value = name, onChange = handleChange }
+ )
+ end
+
+ local container = Instance.new("Frame") -- This element has to be in the for the event system to work.
+ -document.body:appendChild(container) -- It's important that this test uses legacy sync mode.
+ -- The root API does not trigger this particular failing case.
+ ReactRoblox.createRoot(container):render(React.createElement(ControlledInput, nil))
+ act(function()
+ return store._profilerStore:startProfiling()
+ end)
+
+ -- Sets a value in a way that React doesn't see,
+ -- so that a subsequent "change" event will trigger the event handler.
+ local setUntrackedValue = Object.getOwnPropertyDescriptor(
+ HTMLInputElement.prototype,
+ "value"
+ ).set
+
+ local target = inputRef.current
+ setUntrackedValue(target, "bar")
+ target:dispatchEvent(
+ Event.new("input", { bubbles = true, cancelable = true })
+ )
+ jestExpect(target.value).toBe("bar")
+
+ act(function()
+ return store._profilerStore:stopProfiling()
+ end)
+
+ -- Only one commit should have been recorded (in response to the "change" event).
+ local root = store:getRoots()[1]
+ local data = store._profilerStore:getDataForRoot(root)
+ jestExpect(data.commitData).toHaveLength(1)
+ jestExpect(data.operations).toHaveLength(1)
+ end)
+ --]]
+end)
diff --git a/modules/react-devtools-shared/src/__tests__/profilingCache.spec.lua b/modules/react-devtools-shared/src/__tests__/profilingCache.spec.lua
new file mode 100644
index 00000000..4da3d8ab
--- /dev/null
+++ b/modules/react-devtools-shared/src/__tests__/profilingCache.spec.lua
@@ -0,0 +1,939 @@
+--!strict
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.1/packages/react-devtools-shared/src/__tests__/profilingCache-test.js
+--[[*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ ]]
+
+local Packages = script.Parent.Parent.Parent
+local ReactGlobals = require(Packages.ReactGlobals)
+local JestGlobals = require(Packages.Dev.JestGlobals)
+local jestExpect = JestGlobals.expect
+local xdescribe = JestGlobals.xdescribe
+local it = JestGlobals.it
+local xit = JestGlobals.xit
+local beforeEach = JestGlobals.beforeEach
+local afterEach = JestGlobals.afterEach
+
+local LuauPolyfill = require(Packages.LuauPolyfill)
+local Error = LuauPolyfill.Error
+
+local Bridge = require(script.Parent.Parent.bridge)
+type FrontendBridge = Bridge.FrontendBridge
+
+local devtoolsTypes = require(script.Parent.Parent.devtools.types)
+type Store = devtoolsTypes.Store
+
+local ProfilerTypes = require(script.Parent.Parent.devtools.views.Profiler.types)
+type ProfilingDataFrontend = ProfilerTypes.ProfilingDataFrontend
+
+local global = ReactGlobals :: any
+
+-- ROBLOX deviation START: inline simplified PropTypes logic
+-- ROBLOX FIXME luau: if not annotated, gets 'Failed ot unify type packs'
+local function propTypes(value: any, expectedType): any
+ if value == nil then
+ return nil
+ end
+ if type(value) ~= expectedType then
+ return Error("expected " .. expectedType)
+ end
+ return nil
+end
+local PropTypes = {
+ number = function(props, typeSpecName)
+ return propTypes(props[typeSpecName], "number")
+ end,
+ string = function(props, typeSpecName)
+ return propTypes(props[typeSpecName], "string")
+ end,
+}
+-- ROBLOX deviation END
+
+xdescribe("ProfilingCache", function()
+ local React
+ local ReactRoblox
+ local Scheduler
+ local SchedulerTracing
+ local bridge: FrontendBridge
+ local store: Store
+ local utils
+ local act
+
+ beforeEach(function()
+ ReactGlobals.__PROFILE__ = true
+ utils = require(script.Parent.utils)
+ act = utils.act
+
+ bridge = global.bridge
+ store = global.store
+ store:setCollapseNodesByDefault(false)
+ store:setRecordChangeDescriptions(true)
+
+ --PropTypes = require_("prop-types")
+ React = require(Packages.React)
+ ReactRoblox = require(Packages.ReactRoblox)
+ Scheduler = require(Packages.Dev.Scheduler)
+ SchedulerTracing = Scheduler.tracing
+
+ utils.beforeEachProfiling()
+ end)
+
+ afterEach(function()
+ ReactGlobals.__PROFILE__ = nil
+ end)
+
+ it(
+ "should collect data for each root (including ones added or mounted after profiling started)",
+ function()
+ local function Child(props)
+ Scheduler.unstable_advanceTime(props.duration)
+ return nil
+ end
+
+ local MemoizedChild = React.memo(Child)
+
+ local function Parent(props)
+ Scheduler.unstable_advanceTime(10)
+
+ local count = props.count
+ local children = table.create(count) :: any
+ for index = 0, count - 1 do
+ children[index + 1] =
+ React.createElement(Child, { key = index, duration = index })
+ end
+
+ return React.createElement(
+ React.Fragment,
+ nil,
+ children,
+ React.createElement(MemoizedChild, { duration = 1 })
+ )
+ end
+
+ local containerA = ReactRoblox.createRoot(Instance.new("Frame"))
+ local containerB = ReactRoblox.createRoot(Instance.new("Frame"))
+ local containerC = ReactRoblox.createRoot(Instance.new("Frame"))
+ act(function()
+ return containerA:render(React.createElement(Parent, { count = 2 }))
+ end)
+
+ act(function()
+ return containerB:render(React.createElement(Parent, { count = 1 }))
+ end)
+ act(function()
+ return store._profilerStore:startProfiling()
+ end)
+ act(function()
+ return containerA:render(React.createElement(Parent, { count = 3 }))
+ end)
+ act(function()
+ return containerC:render(React.createElement(Parent, { count = 1 }))
+ end)
+ act(function()
+ return containerA:render(React.createElement(Parent, { count = 1 }))
+ end)
+ act(function()
+ return containerB:render(nil)
+ end)
+ act(function()
+ return containerA:render(React.createElement(Parent, { count = 0 }))
+ end)
+ act(function()
+ return store._profilerStore:stopProfiling()
+ end)
+
+ local allProfilingDataForRoots = {}
+ local function Validator(rootID, previousProfilingDataForRoot)
+ local profilingDataForRoot = store._profilerStore:getDataForRoot(rootID)
+
+ if previousProfilingDataForRoot ~= nil then
+ jestExpect(profilingDataForRoot).toEqual(previousProfilingDataForRoot)
+ else
+ jestExpect(profilingDataForRoot).toMatchSnapshot(
+ string.format(
+ "Data for root %s",
+ tostring(profilingDataForRoot.displayName)
+ )
+ )
+ end
+ table.insert(allProfilingDataForRoots, profilingDataForRoot)
+ end
+
+ local profilingData = store._profilerStore:profilingData()
+ local dataForRoots = if profilingData ~= nil
+ then profilingData.dataForRoots
+ else nil
+ jestExpect(dataForRoots).never.toBeNull()
+
+ if dataForRoots ~= nil then
+ dataForRoots:forEach(function(dataForRoot)
+ Validator(dataForRoot.rootID, nil)
+ end)
+ end
+ jestExpect(#allProfilingDataForRoots).toBe(3)
+ utils.exportImportHelper(bridge, store)
+
+ for _, profilingDataForRoot in allProfilingDataForRoots do
+ Validator(profilingDataForRoot.rootID, profilingDataForRoot)
+ end
+ end
+ )
+ it("should collect data for each commit", function()
+ local MemoizedChild, Child
+ local function Parent(props)
+ Scheduler.unstable_advanceTime(10)
+
+ local count = props.count
+ local children = table.create(count) :: any
+ for index = 0, count - 1 do
+ children[index + 1] =
+ React.createElement(Child, { key = index, duration = index })
+ end
+
+ return React.createElement(
+ React.Fragment,
+ nil,
+ children,
+ React.createElement(MemoizedChild, { key = -1, duration = 1 })
+ )
+ end
+ function Child(ref)
+ local duration = ref.duration
+ Scheduler.unstable_advanceTime(duration)
+ return nil
+ end
+ MemoizedChild = React.memo(Child)
+
+ -- ROBLOX deviation START: use Roblox renderer
+ local container = ReactRoblox.createRoot(Instance.new("Frame"))
+ -- ROBLOX deviation END
+
+ act(function()
+ return store._profilerStore:startProfiling()
+ end)
+ act(function()
+ return container:render(React.createElement(Parent, { count = 2 }))
+ end)
+ act(function()
+ return container:render(React.createElement(Parent, { count = 3 }))
+ end)
+ act(function()
+ return container:render(React.createElement(Parent, { count = 1 }))
+ end)
+ act(function()
+ return container:render(React.createElement(Parent, { count = 0 }))
+ end)
+ act(function()
+ return store._profilerStore:stopProfiling()
+ end)
+ local allCommitData = {}
+ local function Validator(ref)
+ local commitIndex, previousCommitDetails, rootID =
+ ref.commitIndex, ref.previousCommitDetails, ref.rootID
+ local commitData = store._profilerStore:getCommitData(rootID, commitIndex)
+ if previousCommitDetails ~= nil then
+ jestExpect(commitData).toEqual(previousCommitDetails)
+ else
+ table.insert(allCommitData, commitData)
+ jestExpect(commitData).toMatchSnapshot(
+ string.format("CommitDetails commitIndex: %d", commitIndex - 1)
+ )
+ end
+ end
+ local rootID = store:getRoots()[1]
+
+ for commitIndex = 1, 4 do
+ Validator({
+ commitIndex = commitIndex,
+ previousCommitDetails = nil,
+ rootID = rootID,
+ })
+ end
+ jestExpect(#allCommitData).toBe(4)
+ utils.exportImportHelper(bridge, store)
+
+ for commitIndex = 1, 4 do
+ Validator({
+ commitIndex = commitIndex,
+ previousCommitDetails = allCommitData[commitIndex],
+ rootID = rootID,
+ })
+ end
+ end)
+ it("should record changed props/state/context/hooks", function()
+ local LegacyContextConsumer, ModernContextConsumer
+ local instance = nil
+ local ModernContext = React.createContext(0)
+
+ local LegacyContextProvider = React.Component:extend("LegacyContextProvider")
+ LegacyContextProvider.childContextTypes = { count = PropTypes.number }
+
+ function LegacyContextProvider:init()
+ self:setState({ count = 0 })
+ end
+ function LegacyContextProvider:getChildContext()
+ return self.state
+ end
+ function LegacyContextProvider:render()
+ return React.createElement(
+ ModernContext.Provider,
+ {
+ value = self.state.count,
+ },
+ React.createElement(React.Fragment, nil, {
+ React.createElement(ModernContextConsumer),
+ React.createElement(LegacyContextConsumer),
+ })
+ )
+ end
+
+ local function FunctionComponentWithHooks(ref)
+ local count = ref.count
+ React.useMemo(function()
+ return count
+ end, { count })
+ return nil
+ end
+ ModernContextConsumer = React.Component:extend("ModernContextConsumer")
+ function ModernContextConsumer:render()
+ return React.createElement(
+ FunctionComponentWithHooks,
+ { count = self.context }
+ )
+ end
+
+ LegacyContextConsumer = React.Component:extend("LegacyContextConsumer")
+ function LegacyContextConsumer:render()
+ instance = self
+ return React.createElement(
+ FunctionComponentWithHooks,
+ { count = self.context.count }
+ )
+ end
+
+ local container = ReactRoblox.createRoot(Instance.new("Frame"))
+ act(function()
+ return store._profilerStore:startProfiling()
+ end)
+ act(function()
+ return container:render(React.createElement(LegacyContextProvider, nil))
+ end)
+ jestExpect(instance).never.toBeNull()
+ act(function()
+ return (instance :: any):setState({ count = 1 })
+ end)
+ act(function()
+ return container:render(
+ React.createElement(LegacyContextProvider, { foo = 123 })
+ )
+ end)
+ act(function()
+ return container:render(
+ React.createElement(LegacyContextProvider, { bar = "abc" })
+ )
+ end)
+ act(function()
+ return container:render(React.createElement(LegacyContextProvider, nil))
+ end)
+ act(function()
+ return store._profilerStore:stopProfiling()
+ end)
+ local allCommitData = {}
+ local function Validator(ref)
+ local commitIndex, previousCommitDetails, rootID =
+ ref.commitIndex, ref.previousCommitDetails, ref.rootID
+ local commitData = store._profilerStore:getCommitData(rootID, commitIndex)
+ if previousCommitDetails ~= nil then
+ jestExpect(commitData).toEqual(previousCommitDetails)
+ else
+ table.insert(allCommitData, commitData)
+ jestExpect(commitData).toMatchSnapshot(
+ string.format("CommitDetails commitIndex: %d", commitIndex - 1)
+ )
+ end
+ end
+ local rootID = store:getRoots()[1]
+ for commitIndex = 1, 5 do
+ Validator({
+ commitIndex = commitIndex,
+ previousCommitDetails = nil,
+ rootID = rootID,
+ })
+ end
+ jestExpect(allCommitData).toHaveLength(5)
+ utils.exportImportHelper(bridge, store)
+ for commitIndex = 1, 5 do
+ Validator({
+ commitIndex = commitIndex,
+ previousCommitDetails = allCommitData[commitIndex],
+ rootID = rootID,
+ })
+ end
+ end)
+
+ -- ROBLOX FIXME: upstream has priorityLevel as "Immediate"
+ -- ROBLOX FIXME: upstream has didHooksChange as false in this step. maybe related to the priorityLevel difference below?
+ -- ROBLOX Note: These bugs only happen in CommitIndex 1 1
+ it("should properly detect changed hooks", function()
+ local Context = React.createContext(0)
+ local function reducer(state, action)
+ if action.type == "invert" then
+ return { value = not state.value }
+ else
+ error(Error.new())
+ end
+ end
+ local dispatch
+ local setState
+ local function Component(ref)
+ local string_ = ref.string
+ local _
+ -- These hooks may change and initiate re-renders.
+ _, setState = React.useState("abc")
+ -- ROBLOX FIXME Luau: without this any cast, Type '{| value: boolean |}' could not be converted into 'string'
+ _, dispatch = React.useReducer(reducer :: any, { value = true })
+
+ -- This hook's return value may change between renders,
+ -- but the hook itself isn't stateful.
+ React.useContext(Context)
+
+ -- These hooks and their dependencies may not change between renders.
+ -- We're using them to ensure that they don't trigger false positives.
+ React.useCallback(function()
+ return function() end
+ end, { string_ })
+ React.useMemo(function()
+ return string_
+ end, { string_ })
+
+ -- These hooks never "change".
+ React.useEffect(function() end, { string_ })
+ React.useLayoutEffect(function() end, { string_ })
+
+ return nil
+ end
+
+ local container = ReactRoblox.createRoot(Instance.new("Frame"))
+ act(function()
+ return store._profilerStore:startProfiling()
+ end)
+ act(function()
+ return container:render(
+ React.createElement(
+ Context.Provider,
+ { value = true },
+ React.createElement(Component, { count = 1 })
+ )
+ )
+ end)
+
+ -- Second render has no changed hooks, only changed props.
+ act(function()
+ return container:render(
+ React.createElement(
+ Context.Provider,
+ { value = true },
+ React.createElement(Component, { count = 2 })
+ )
+ )
+ end)
+
+ -- Third render has a changed reducer hook
+ act(function()
+ return dispatch({ type = "invert" })
+ end)
+
+ -- Fourth render has a changed state hook
+ act(function()
+ return setState("def")
+ end)
+
+ -- Fifth render has a changed context value, but no changed hook.
+ -- Technically, DevTools will miss this "context" change since it only tracks legacy context.
+ act(function()
+ return container:render(
+ React.createElement(
+ Context.Provider,
+ { value = false },
+ React.createElement(Component, { count = 2 })
+ )
+ )
+ end)
+
+ act(function()
+ return store._profilerStore:stopProfiling()
+ end)
+
+ local allCommitData = {}
+
+ local function Validator(ref)
+ local commitIndex, previousCommitDetails, rootID =
+ ref.commitIndex, ref.previousCommitDetails, ref.rootID
+ local commitData = store._profilerStore:getCommitData(rootID, commitIndex)
+ if previousCommitDetails ~= nil then
+ jestExpect(commitData).toEqual(previousCommitDetails)
+ else
+ table.insert(allCommitData, commitData)
+ jestExpect(commitData).toMatchSnapshot(
+ string.format(
+ "CommitDetails commitIndex: %s",
+ tostring(commitIndex - 1)
+ )
+ )
+ end
+ return nil
+ end
+ local rootID = store:getRoots()[1]
+ for commitIndex = 1, 5 do
+ Validator({
+ commitIndex = commitIndex,
+ previousCommitDetails = nil,
+ rootID = rootID,
+ })
+ end
+
+ jestExpect(allCommitData).toHaveLength(5)
+
+ -- Export and re-import profile data and make sure it is retained.
+ utils.exportImportHelper(bridge, store)
+
+ for commitIndex = 1, 5 do
+ Validator({
+ commitIndex = commitIndex,
+ previousCommitDetails = allCommitData[commitIndex],
+ rootID = rootID,
+ })
+ end
+ end)
+ it(
+ "should calculate a self duration based on actual children (not filtered children)",
+ function()
+ local Parent, Child
+ store:setComponentFilters({ utils.createDisplayNameFilter("^Parent$") })
+ local function Grandparent()
+ Scheduler.unstable_advanceTime(10)
+ return React.createElement(
+ React.Fragment,
+ nil,
+ React.createElement(Parent, { key = "one" }),
+ React.createElement(Parent, { key = "two" })
+ )
+ end
+ function Parent()
+ Scheduler.unstable_advanceTime(2)
+ return React.createElement(Child, nil)
+ end
+ function Child()
+ Scheduler.unstable_advanceTime(1)
+ return nil
+ end
+ act(function()
+ return store._profilerStore:startProfiling()
+ end)
+ act(function()
+ return ReactRoblox.createRoot(Instance.new("Frame"))
+ :render(React.createElement(Grandparent, nil))
+ end)
+ act(function()
+ return store._profilerStore:stopProfiling()
+ end)
+ local commitData = nil
+ local function Validator(ref)
+ local commitIndex, rootID = ref.commitIndex, ref.rootID
+ commitData = store._profilerStore:getCommitData(rootID, commitIndex)
+ jestExpect(commitData).toMatchSnapshot(
+ "CommitDetails with filtered self durations"
+ )
+ end
+ local rootID = store:getRoots()[1]
+ Validator({ commitIndex = 1, rootID = rootID })
+ jestExpect(commitData).never.toBeNull()
+ end
+ )
+ --[=[
+ xit("should calculate self duration correctly for suspended views", function(done)
+ local Fallback, Async
+ return Promise.resolve():andThen(function()
+ local data
+ local function getData()
+ if data then
+ return data
+ else
+ error(Promise.new(function(resolve)
+ data = "abc"
+ resolve(data)
+ end))
+ end
+ end
+ local function Parent()
+ Scheduler.unstable_advanceTime(10)
+ return React.createElement(
+ React.Suspense,
+ { fallback = React.createElement(Fallback, nil) },
+ React.createElement(Async, nil)
+ )
+ end
+ function Fallback()
+ Scheduler.unstable_advanceTime(2)
+ return "Fallback..."
+ end
+ function Async()
+ Scheduler.unstable_advanceTime(3)
+ return getData()
+ end
+ act(function()
+ return store._profilerStore:startProfiling()
+ end)
+ utils
+ :actAsync(function()
+ return ReactDOM:render(
+ React.createElement(Parent, nil),
+ document:createElement("div")
+ )
+ end)
+ :jestExpect()
+ act(function()
+ return store._profilerStore:stopProfiling()
+ end)
+ local allCommitData = {}
+ local function Validator(ref)
+ local commitIndex, rootID = ref.commitIndex, ref.rootID
+ local commitData = store._profilerStore:getCommitData(
+ rootID,
+ commitIndex
+ )
+ table.insert(allCommitData, commitData) --[[ ROBLOX CHECK: check if 'allCommitData' is an Array ]]
+ jestExpect(commitData).toMatchSnapshot(
+ "CommitDetails with filtered self durations"
+ )
+ return nil
+ end
+ local rootID = store.roots[
+ 1 --[[ ROBLOX adaptation: added 1 to array index ]]
+ ]
+ do
+ local function _loop(commitIndex)
+ act(function()
+ TestRenderer:create(
+ React.createElement(
+ Validator,
+ { commitIndex = commitIndex, rootID = rootID }
+ )
+ )
+ end)
+ end
+ local commitIndex = 0
+ while
+ commitIndex
+ < 2 --[[ ROBLOX CHECK: operator '<' works only if either both arguments are strings or both are a number ]]
+ do
+ _loop(commitIndex)
+ commitIndex += 1
+ end
+ end
+ jestExpect(allCommitData).toHaveLength(2)
+ done()
+ end)
+ end)
+ --]=]
+ it("should collect data for each rendered fiber", function()
+ local MemoizedChild, Child
+ local function Parent(props)
+ Scheduler.unstable_advanceTime(10)
+
+ local count = props.count
+ local children = table.create(count) :: any
+ for index = 0, count - 1 do
+ children[index + 1] =
+ React.createElement(Child, { key = index, duration = index })
+ end
+
+ return React.createElement(
+ React.Fragment,
+ nil,
+ children,
+ React.createElement(MemoizedChild, { key = -1, duration = 1 })
+ )
+ end
+ function Child(ref)
+ local duration = ref.duration
+ Scheduler.unstable_advanceTime(duration)
+ return nil
+ end
+ MemoizedChild = React.memo(Child)
+
+ local container = ReactRoblox.createRoot(Instance.new("Frame"))
+ act(function()
+ return store._profilerStore:startProfiling()
+ end)
+ act(function()
+ return container:render(React.createElement(Parent, { count = 1 }))
+ end)
+ act(function()
+ return container:render(React.createElement(Parent, { count = 2 }))
+ end)
+ act(function()
+ return container:render(React.createElement(Parent, { count = 3 }))
+ end)
+ act(function()
+ return store._profilerStore:stopProfiling()
+ end)
+
+ local allFiberCommits = {}
+ local function Validator(ref)
+ local fiberID, previousFiberCommits, rootID =
+ ref.fiberID, ref.previousFiberCommits, ref.rootID
+ local fiberCommits = store._profilerStore:profilingCache():getFiberCommits({
+ fiberID = fiberID,
+ rootID = rootID,
+ })
+ if previousFiberCommits ~= nil then
+ jestExpect(fiberCommits).toEqual(previousFiberCommits)
+ else
+ table.insert(allFiberCommits, fiberCommits)
+ jestExpect(fiberCommits).toMatchSnapshot(
+ string.format("FiberCommits: element %d", fiberID)
+ )
+ end
+ end
+ local rootID = store:getRoots()[1]
+
+ for index = 0, store:getNumElements() - 1 do
+ local fiberID = store:getElementIDAtIndex(index)
+ if fiberID == nil then
+ error(
+ string.format(
+ "Unexpected null ID for element at index %s",
+ tostring(index)
+ )
+ )
+ end
+
+ Validator({
+ fiberID = fiberID,
+ previousFiberCommits = nil,
+ rootID = rootID,
+ })
+ end
+
+ jestExpect(allFiberCommits).toHaveLength(store:getNumElements())
+ utils.exportImportHelper(bridge, store)
+
+ --[=[ ROBLOX FIXME: 0-based indexing gets ruined by deserializing
+ for index = 0, store:getNumElements() - 1 do
+ local fiberID = store:getElementIDAtIndex(index)
+ if fiberID == nil then
+ error(
+ string.format(
+ "Unexpected null ID for element at index %s",
+ tostring(index)
+ )
+ )
+ end
+
+ Validator({
+ fiberID = fiberID,
+ previousFiberCommits = allFiberCommits[index],
+ rootID = rootID,
+ })
+ end
+ --]=]
+ end)
+ it("should report every traced interaction", function()
+ local MemoizedChild, Child
+ local function Parent(props)
+ Scheduler.unstable_advanceTime(10)
+
+ local count = props.count
+ local children = table.create(count) :: any
+ for index = 0, count - 1 do
+ children[index + 1] =
+ React.createElement(Child, { key = index, duration = index })
+ end
+
+ return React.createElement(
+ React.Fragment,
+ nil,
+ children,
+ React.createElement(MemoizedChild, { key = -1, duration = 1 })
+ )
+ end
+ function Child(ref)
+ local duration = ref.duration
+ Scheduler.unstable_advanceTime(duration)
+ return nil
+ end
+ MemoizedChild = React.memo(Child)
+
+ local container = ReactRoblox.createRoot(Instance.new("Frame"))
+ act(function()
+ return store._profilerStore:startProfiling()
+ end)
+ act(function()
+ return SchedulerTracing.unstable_trace(
+ "mount: one child",
+ Scheduler.unstable_now(),
+ function()
+ return container:render(React.createElement(Parent, { count = 1 }))
+ end
+ )
+ end)
+ act(function()
+ return SchedulerTracing.unstable_trace(
+ "update: two children",
+ Scheduler.unstable_now(),
+ function()
+ return container:render(React.createElement(Parent, { count = 2 }))
+ end
+ )
+ end)
+ act(function()
+ return store._profilerStore:stopProfiling()
+ end)
+ local interactions = nil
+ local function Validator(ref)
+ local previousInteractions, rootID = ref.previousInteractions, ref.rootID
+ interactions =
+ store._profilerStore:profilingCache():getInteractionsChartData({
+ rootID = rootID,
+ }).interactions
+ -- ROBLOX FIXME: interactions[0] supposed to have __count=1, but it's 0 once it gets to the ProfilerStore. it's correct in WorkLoop and Tracing.
+ if previousInteractions ~= nil then
+ jestExpect(interactions).toEqual(previousInteractions)
+ else
+ jestExpect(interactions).toMatchSnapshot("Interactions")
+ end
+ return nil
+ end
+ local rootID = store:getRoots()[1]
+ Validator({ previousInteractions = nil, rootID = rootID })
+
+ jestExpect(interactions).never.toBeNull()
+ utils.exportImportHelper(bridge, store)
+
+ Validator({ previousInteractions = interactions, rootID = rootID })
+ end)
+ it("should handle unexpectedly shallow suspense trees", function()
+ local container = ReactRoblox.createRoot(Instance.new("Frame"))
+ act(function()
+ return store._profilerStore:startProfiling()
+ end)
+ act(function()
+ return container:render(React.createElement(React.Suspense, nil))
+ end)
+ act(function()
+ return store._profilerStore:stopProfiling()
+ end)
+
+ local rootID = store:getRoots()[1]
+ local profilingDataForRoot = store._profilerStore:getDataForRoot(rootID)
+ jestExpect(profilingDataForRoot).toMatchSnapshot("Empty Suspense node")
+ end)
+
+ -- ROBLOX TODO: needs a textContent helper for ReactRoblox renderers
+ -- See https://github.com/facebook/react/issues/18831
+ xit("should not crash during route transitions with Suspense", function()
+ local Router, Switch, Route, About, Home, Link
+ local RouterContext = React.createContext(nil)
+ local function App()
+ return React.createElement(
+ Router,
+ nil,
+ React.createElement(
+ Switch,
+ nil,
+ React.createElement(
+ Route,
+ { path = "/" },
+ React.createElement(Home, nil)
+ ),
+ React.createElement(
+ Route,
+ { path = "/about" },
+ React.createElement(About, nil)
+ )
+ )
+ )
+ end
+ function Home()
+ return React.createElement(
+ React.Suspense,
+ nil,
+ React.createElement(
+ Link,
+ { path = "/about" },
+ React.createElement("TextLabel", { Text = "Home" })
+ )
+ )
+ end
+ function About()
+ return React.createElement("TextLabel", { Text = "About" })
+ end
+
+ -- Mimics https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Router.js
+ function Router(ref)
+ local children = ref.children
+ local path, setPath = React.useState("/")
+ return React.createElement(
+ RouterContext.Provider,
+ { value = { path = path, setPath = setPath } },
+ children
+ )
+ end
+
+ -- Mimics https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Switch.js
+ function Switch(ref)
+ local children = ref.children
+ return React.createElement(RouterContext.Consumer, nil, function(context)
+ local element = nil
+ React.Children.forEach(children, function(child: any)
+ if context.path == child.props.path then
+ element = child.props.children
+ end
+ end)
+ return if element then React.cloneElement(element) else nil
+ end)
+ end
+
+ -- Mimics https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Route.js
+ function Route(ref)
+ return nil
+ end
+ local linkRef = React.createRef()
+
+ -- Mimics https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/Link.js
+ function Link(ref)
+ local children, path = ref.children, ref.path
+ return React.createElement(RouterContext.Consumer, nil, function(context)
+ return React.createElement("TextButton", {
+ ref = linkRef,
+ [ReactRoblox.Event.Activated] = function()
+ return context:setPath(path)
+ end,
+ }, children)
+ end)
+ end
+ -- ROBLOX TODO: emulate this and uncomment the expect
+ -- local Simulate = require_("react-dom/test-utils").Simulate
+ local container = ReactRoblox.createRoot(Instance.new("Frame"))
+ act(function()
+ return container:render(React.createElement(App))
+ end)
+ jestExpect(container.textContent).toBe("Home")
+ act(function()
+ return store._profilerStore:startProfiling()
+ end)
+ -- act(function()
+ -- return Simulate:click(linkRef.current)
+ -- end)
+ act(function()
+ return store._profilerStore:stopProfiling()
+ end)
+ jestExpect(container.textContent).toBe("About")
+ end)
+end)
diff --git a/modules/react-devtools-shared/src/__tests__/profilingCharts.spec.lua b/modules/react-devtools-shared/src/__tests__/profilingCharts.spec.lua
new file mode 100644
index 00000000..bc6dee2f
--- /dev/null
+++ b/modules/react-devtools-shared/src/__tests__/profilingCharts.spec.lua
@@ -0,0 +1,289 @@
+--!strict
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.1/packages/react-devtools-shared/src/__tests__/profilingCharts-test.js
+--[[*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ ]]
+
+local Packages = script.Parent.Parent.Parent
+local ReactGlobals = require(Packages.ReactGlobals)
+local JestGlobals = require(Packages.Dev.JestGlobals)
+local xdescribe = JestGlobals.xdescribe
+local beforeEach = JestGlobals.beforeEach
+local afterEach = JestGlobals.afterEach
+local describe = JestGlobals.describe
+local it = JestGlobals.it
+local jestExpect = JestGlobals.expect
+
+local devtoolsTypes = require(script.Parent.Parent.devtools.types)
+type Store = devtoolsTypes.Store
+
+xdescribe("profiling charts", function()
+ local React
+ local ReactRoblox
+ local Scheduler
+ local SchedulerTracing
+ -- local TestRenderer
+ local store: Store
+ local utils
+ beforeEach(function()
+ ReactGlobals.__PROFILE__ = true
+ utils = require(script.Parent.utils)
+ utils.beforeEachProfiling()
+
+ store = (ReactGlobals :: any).store
+ store:setCollapseNodesByDefault(false)
+ store:setRecordChangeDescriptions(true)
+ React = require(Packages.React)
+ ReactRoblox = require(Packages.ReactRoblox)
+ Scheduler = require(Packages.Dev.Scheduler)
+ SchedulerTracing = Scheduler.tracing
+ end)
+ afterEach(function()
+ ReactGlobals.__PROFILE__ = nil
+ end)
+ describe("flamegraph chart", function()
+ it("should contain valid data", function()
+ local Child
+ local function Parent(_: {})
+ Scheduler.unstable_advanceTime(10)
+ return React.createElement(
+ React.Fragment,
+ nil,
+ React.createElement(Child, { key = "first", duration = 3 }),
+ React.createElement(Child, { key = "second", duration = 2 }),
+ React.createElement(Child, { key = "third", duration = 0 })
+ )
+ end
+
+ -- Memoize children to verify that chart doesn't include in the update.
+ function Child(ref)
+ local duration = ref.duration
+ Scheduler.unstable_advanceTime(duration)
+ return nil
+ end
+ -- ROBLOX FIXME Luau: greediness means first type assignment wins, needs DCR
+ Child = React.memo(Child) :: any
+
+ local container = ReactRoblox.createRoot(Instance.new("Frame"))
+ utils.act(function()
+ return store._profilerStore:startProfiling()
+ end)
+ utils.act(function()
+ return SchedulerTracing.unstable_trace(
+ "mount",
+ Scheduler.unstable_now(),
+ function()
+ return container:render(React.createElement(Parent))
+ end
+ )
+ end)
+ utils.act(function()
+ return SchedulerTracing.unstable_trace(
+ "update",
+ Scheduler.unstable_now(),
+ function()
+ return container:render(React.createElement(Parent))
+ end
+ )
+ end)
+ utils.act(function()
+ return store._profilerStore:stopProfiling()
+ end)
+ local renderFinished = false
+ local function Validator(ref)
+ local commitIndex, rootID = ref.commitIndex, ref.rootID
+ local commitTree = store._profilerStore:profilingCache():getCommitTree({
+ commitIndex = commitIndex,
+ rootID = rootID,
+ })
+ local chartData =
+ store._profilerStore:profilingCache():getFlamegraphChartData({
+ commitIndex = commitIndex,
+ commitTree = commitTree,
+ rootID = rootID,
+ })
+ jestExpect(commitTree).toMatchSnapshot(
+ ("%s: CommitTree"):format(tostring(commitIndex - 1))
+ )
+ jestExpect(chartData).toMatchSnapshot(
+ ("%s: FlamegraphChartData"):format(tostring(commitIndex - 1))
+ )
+ renderFinished = true
+ return nil
+ end
+ local rootID = store:getRoots()[1]
+
+ for commitIndex = 1, 2 do
+ renderFinished = false
+ Validator({
+ commitIndex = commitIndex,
+ rootID = rootID,
+ })
+ end
+ jestExpect(renderFinished).toBe(true)
+ end)
+ end)
+
+ xdescribe("ranked chart", function()
+ -- ROBLOX FIXME: the "type" of the children in our snap is 5, but in upstream it's 8, every matches except...
+ it("should contain valid data", function()
+ local Child
+ local function Parent(_: {})
+ Scheduler.unstable_advanceTime(10)
+ return React.createElement(
+ React.Fragment,
+ nil,
+ React.createElement(Child, { key = "first", duration = 3 }),
+ React.createElement(Child, { key = "second", duration = 2 }),
+ React.createElement(Child, { key = "third", duration = 0 })
+ )
+ end
+
+ -- Memoize children to verify that chart doesn't include in the update.
+ function Child(ref)
+ local duration = ref.duration
+ Scheduler.unstable_advanceTime(duration)
+ return nil
+ end
+ -- ROBLOX FIXME Luau: greediness means first type assignment wins, needs DCR
+ Child = React.memo(Child) :: any
+ local container = ReactRoblox.createRoot(Instance.new("Frame"))
+ utils.act(function()
+ return store._profilerStore:startProfiling()
+ end)
+
+ utils.act(function()
+ return SchedulerTracing.unstable_trace(
+ "mount",
+ Scheduler.unstable_now(),
+ function()
+ return container:render(React.createElement(Parent))
+ end
+ )
+ end)
+
+ utils.act(function()
+ return SchedulerTracing.unstable_trace(
+ "update",
+ Scheduler.unstable_now(),
+ function()
+ return container:render(React.createElement(Parent))
+ end
+ )
+ end)
+
+ utils.act(function()
+ return store._profilerStore:stopProfiling()
+ end)
+ local renderFinished = false
+ local function Validator(ref)
+ local commitIndex, rootID = ref.commitIndex, ref.rootID
+ local commitTree = store._profilerStore:profilingCache():getCommitTree({
+ commitIndex = commitIndex,
+ rootID = rootID,
+ })
+ local chartData =
+ store._profilerStore:profilingCache():getRankedChartData({
+ commitIndex = commitIndex,
+ commitTree = commitTree,
+ rootID = rootID,
+ })
+ jestExpect(commitTree).toMatchSnapshot(
+ ("%s: CommitTree"):format(tostring(commitIndex - 1))
+ )
+ jestExpect(chartData).toMatchSnapshot(
+ ("%s: RankedChartData"):format(tostring(commitIndex - 1))
+ )
+ renderFinished = true
+ return nil
+ end
+ local rootID = store:getRoots()[1]
+
+ for commitIndex = 1, 2 do
+ renderFinished = false
+ Validator({
+ commitIndex = commitIndex,
+ rootID = rootID,
+ })
+ jestExpect(renderFinished).toBe(true)
+ end
+ jestExpect(renderFinished).toBe(true)
+ end)
+ end)
+ xdescribe("interactions", function()
+ it("should contain valid data", function()
+ local Child
+ local function Parent(_: {})
+ Scheduler.unstable_advanceTime(10)
+ return React.createElement(
+ React.Fragment,
+ nil,
+ React.createElement(Child, { key = "first", duration = 3 }),
+ React.createElement(Child, { key = "second", duration = 2 }),
+ React.createElement(Child, { key = "third", duration = 0 })
+ )
+ end
+
+ -- Memoize children to verify that chart doesn't include in the update.
+ function Child(ref)
+ local duration = ref.duration
+ Scheduler.unstable_advanceTime(duration)
+ return nil
+ end
+ -- ROBLOX FIXME Luau: greediness means first type assignment wins, needs DCR
+ Child = React.memo(Child) :: any
+ local container = ReactRoblox.createRoot(Instance.new("Frame"))
+ utils.act(function()
+ return store._profilerStore:startProfiling()
+ end)
+ utils.act(function()
+ return SchedulerTracing.unstable_trace(
+ "mount",
+ Scheduler.unstable_now(),
+ function()
+ return container:render(React.createElement(Parent))
+ end
+ )
+ end)
+
+ utils.act(function()
+ return SchedulerTracing.unstable_trace(
+ "update",
+ Scheduler.unstable_now(),
+ function()
+ return container:render(React.createElement(Parent))
+ end
+ )
+ end)
+ utils.act(function()
+ return store._profilerStore:stopProfiling()
+ end)
+ local renderFinished = false
+ local function Validator(ref)
+ local _commitIndex, rootID = ref.commitIndex, ref.rootID
+ local chartData =
+ store._profilerStore:profilingCache():getInteractionsChartData({
+ rootID = rootID,
+ })
+ jestExpect(chartData).toMatchSnapshot("Interactions")
+ renderFinished = true
+ return nil
+ end
+ local rootID = store:getRoots()[1]
+ for commitIndex = 1, 2 do
+ renderFinished = false
+ Validator({
+ commitIndex = commitIndex,
+ rootID = rootID,
+ })
+ jestExpect(renderFinished).toBe(true)
+ end
+ jestExpect(renderFinished).toBe(true)
+ end)
+ end)
+end)
diff --git a/modules/react-devtools-shared/src/__tests__/profilingCommitTreeBuilder.spec.lua b/modules/react-devtools-shared/src/__tests__/profilingCommitTreeBuilder.spec.lua
new file mode 100644
index 00000000..4547bfab
--- /dev/null
+++ b/modules/react-devtools-shared/src/__tests__/profilingCommitTreeBuilder.spec.lua
@@ -0,0 +1,100 @@
+--!nonstrict
+-- ROBLOX FIXME: Type '({| count: number |}) -> {ReactElement<{ key: number }, a>}' could not be converted into '((React_ElementProps?, any) -> (Array<(Array<*CYCLE*> | React_Element | boolean | number | string | t1)?> | React_Element | boolean | number | string | t1)?) | LazyComponent?> | ReactContext | ReactProviderType | React_ComponentType?> | string where t1 = {| [string]: (boolean | number | string | t1 | {(*CYCLE* | boolean | number | string | t1 | {| key: (number | string)?, props: {| __source: {| fileName: string, lineNumber: number |}?, children: any?, key: (number | string)?, ref: (((any?) -> ()) | {| current: any? |})? |}?, ref: any, type: any |})?}... *TRUNCATED*'; none of the union options are compatible
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.1/packages/react-devtools-shared/src/__tests__/profilingCOmmitTreeBuilder-test.js
+--[[*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ ]]
+
+local Packages = script.Parent.Parent.Parent
+local ReactGlobals = require(Packages.ReactGlobals)
+local JestGlobals = require(Packages.Dev.JestGlobals)
+local xdescribe = JestGlobals.xdescribe
+local beforeEach = JestGlobals.beforeEach
+local it = JestGlobals.it
+local jestExpect = JestGlobals.expect
+
+local devtoolsTypes = require(script.Parent.Parent.devtools.types)
+type Store = devtoolsTypes.Store
+
+local global = ReactGlobals :: any
+
+xdescribe("commit tree", function()
+ local React
+ local ReactRoblox
+ local Scheduler
+ local store: Store
+ local utils
+ local act
+
+ beforeEach(function()
+ utils = require(script.Parent.utils)
+ act = utils.act
+ utils.beforeEachProfiling()
+
+ store = global.store
+ store:setCollapseNodesByDefault(false)
+ store:setRecordChangeDescriptions(true)
+
+ React = require(Packages.React)
+ ReactRoblox = require(Packages.ReactRoblox)
+ Scheduler = require(Packages.Dev.Scheduler)
+ end)
+ it("should be able to rebuild the store tree for each commit", function()
+ local function Child()
+ Scheduler.unstable_advanceTime(2)
+ return nil
+ end
+ local ChildMemo = React.memo(Child)
+
+ local function Parent(props: { count: number })
+ local count = props.count
+ Scheduler.unstable_advanceTime(10)
+
+ local Children = table.create(count)
+ for i = 1, count do
+ Children[i] = React.createElement(ChildMemo, { key = i - 1 })
+ end
+
+ return Children
+ end
+
+ local root = ReactRoblox.createRoot(Instance.new("Frame"))
+
+ act(function()
+ store._profilerStore:startProfiling()
+ end)
+ act(function()
+ root:render(React.createElement(Parent, { count = 1 }))
+ end)
+ act(function()
+ root:render(React.createElement(Parent, { count = 3 }))
+ end)
+ act(function()
+ root:render(React.createElement(Parent, { count = 2 }))
+ end)
+ act(function()
+ root:render(React.createElement(Parent, { count = 0 }))
+ end)
+ act(function()
+ store._profilerStore:stopProfiling()
+ end)
+
+ local rootID = store:getRoots()[1]
+ for commitIndex = 1, 4 do
+ local profilingCache = store._profilerStore:profilingCache()
+ local commitTree = profilingCache:getCommitTree({
+ commitIndex = commitIndex,
+ rootID = rootID,
+ })
+
+ jestExpect(commitTree).toMatchSnapshot(
+ string.format("%d: CommitTree", commitIndex - 1)
+ )
+ end
+ end)
+end)
diff --git a/modules/react-devtools-shared/src/__tests__/profilingUtils.spec.lua b/modules/react-devtools-shared/src/__tests__/profilingUtils.spec.lua
new file mode 100644
index 00000000..a369ee16
--- /dev/null
+++ b/modules/react-devtools-shared/src/__tests__/profilingUtils.spec.lua
@@ -0,0 +1,31 @@
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.1/packages/react-devtools-shared/src/__tests__/profilingUtils-test.js
+--[[*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ ]]
+
+local Packages = script.Parent.Parent.Parent
+local JestGlobals = require(Packages.Dev.JestGlobals)
+local jestExpect = JestGlobals.expect
+local describe = JestGlobals.describe
+local it = JestGlobals.it
+local beforeEach = JestGlobals.beforeEach
+
+describe("profiling utils", function()
+ local utils
+ beforeEach(function()
+ utils = require(script.Parent.Parent.devtools.views.Profiler.utils)
+ end)
+ it("should throw if importing older/unsupported data", function()
+ jestExpect(function()
+ return utils.prepareProfilingDataFrontendFromExport({
+ version = 0,
+ dataForRoots = {},
+ })
+ end).toThrow('Unsupported profiler export version "0"')
+ end)
+end)
diff --git a/modules/react-devtools-shared/src/__tests__/setupTests.lua b/modules/react-devtools-shared/src/__tests__/setupTests.lua
new file mode 100644
index 00000000..aa122ac9
--- /dev/null
+++ b/modules/react-devtools-shared/src/__tests__/setupTests.lua
@@ -0,0 +1,97 @@
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.1/packages/react-devtools-shared/src/__tests__/setupTests.js
+--[[**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ *
+ * LICENSE file in the root directory of this source tree.
+ * @flow
+ *]]
+local Packages = script.Parent.Parent.Parent
+local ReactGlobals = require(Packages.ReactGlobals)
+local JestGlobals = require(Packages.Dev.JestGlobals)
+local jestExpect = JestGlobals.expect
+local beforeEach = JestGlobals.beforeEach
+local beforeAll = JestGlobals.beforeAll
+local jest = require(Packages.Dev.JestGlobals).jest
+local afterEach = JestGlobals.afterEach
+
+local LuauPolyfill = require(Packages.LuauPolyfill)
+local Array = LuauPolyfill.Array
+
+local global = ReactGlobals :: any
+
+type Array = { [number]: T }
+
+beforeAll(function()
+ jestExpect.extend({
+ toErrorDev = require(Packages.Parent.jest.matchers.toErrorDev),
+ toWarnDev = require(Packages.Parent.jest.matchers.toWarnDev),
+ })
+end)
+
+beforeEach(function()
+ -- Fake timers let us flush Bridge operations between setup and assertions.
+ jest.useFakeTimers()
+
+ -- These files should be required (and re-required) before each test,
+ -- rather than imported at the head of the module.
+ -- That's because we reset modules between tests,
+ -- which disconnects the DevTool's cache from the current dispatcher ref.
+ local Agent = require(script.Parent.Parent.backend.agent)
+ local initBackend = require(script.Parent.Parent.backend).initBackend
+ local Bridge = require(script.Parent.Parent.bridge)
+ local Store = require(script.Parent.Parent.devtools.store)
+ local installHook = require(script.Parent.Parent.hook).installHook
+ local utils = require(script.Parent.Parent.utils)
+ local getDefaultComponentFilters = utils.getDefaultComponentFilters
+ local saveComponentFilters = utils.saveComponentFilters
+
+ -- Initialize filters to a known good state.
+ saveComponentFilters(getDefaultComponentFilters())
+ global.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = getDefaultComponentFilters()
+
+ installHook(global)
+
+ local bridgeListeners = {}
+ local bridge = Bridge.new({
+ listen = function(callback)
+ table.insert(bridgeListeners, callback)
+ return function()
+ local index = Array.indexOf(bridgeListeners, callback)
+ if index >= 0 then
+ Array.splice(bridgeListeners, index, 1)
+ end
+ end
+ end,
+ send = function(event: string, payload: any, transferable: Array?)
+ for _, callback in bridgeListeners do
+ callback({ event = event, payload = payload })
+ end
+ end,
+ })
+
+ local agent = Agent.new(bridge)
+ local hook = global.__REACT_DEVTOOLS_GLOBAL_HOOK__
+
+ initBackend(hook, agent, global)
+
+ local store = Store.new(bridge)
+
+ global.agent = agent
+ global.bridge = bridge
+ global.store = store
+
+ local ReactFeatureFlags = require(Packages.Shared).ReactFeatureFlags
+ ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = true
+end)
+
+afterEach(function()
+ global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = nil
+
+ -- It's important to reset modules between test runs;
+ -- Without this, ReactDOM won't re-inject itself into the new hook.
+ -- It's also important to reset after tests, rather than before,
+ -- so that we don't disconnect the ReactCurrentDispatcher ref.
+ jest.resetModules()
+end)
diff --git a/modules/react-devtools-shared/src/__tests__/store.spec.lua b/modules/react-devtools-shared/src/__tests__/store.spec.lua
new file mode 100644
index 00000000..3f24ec57
--- /dev/null
+++ b/modules/react-devtools-shared/src/__tests__/store.spec.lua
@@ -0,0 +1,1613 @@
+--!nonstrict
+-- ROBLOX FIXME: TypeError: Type '({+ count: number +}) -> Array>' could not be converted into '((React_ElementProps?, any) -> (Array<(Array<*CYCLE*> | React_Element | boolean | number | string | t1)?> | React_Element | boolean | number | string | t1)?) | LazyComponent?> | ReactContext | ReactProviderType | React_ComponentType?> | string where t1 = {| [string]: (boolean | number | string | t1 | {(*CYCLE* | boolean | number | string | t1 | {| key: (number | string)?, props: {| __source: {| fileName: string, lineNumber: number |}?, children: any?, key: (number | string)?, ref: (((any?) -> ()) | {| current: any? |})? |}?, ref: any, type: any |})?}... *TRUNCATED*'; none of the union options are compatible
+-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.1/packages/react-devtools-shared/src/__tests__/store-test.js
+--[[*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+]]
+
+local fill = function(count, value)
+ local output = {}
+ for i = 1, count do
+ output[i] = value
+ end
+ return output
+end
+
+local Packages = script.Parent.Parent.Parent
+local ReactGlobals = require(Packages.ReactGlobals)
+local JestGlobals = require(Packages.Dev.JestGlobals)
+local jest = JestGlobals.jest
+local jestExpect = JestGlobals.expect
+local describe = JestGlobals.describe
+local it = JestGlobals.it
+local xit = JestGlobals.xit
+local beforeEach = JestGlobals.beforeEach
+
+local Promise = require(Packages.Dev.Promise)
+local LuauPolyfill = require(Packages.LuauPolyfill)
+local Array = LuauPolyfill.Array
+local Object = LuauPolyfill.Object
+
+local global = ReactGlobals :: any
+type Array = { [number]: T }
+
+local ReactRoblox
+local React
+-- ROBLOX deviation: bring in the Agent type
+local Agent = require(script.Parent.Parent.backend.agent)
+type Agent = Agent.Agent
+
+describe("Store", function()
+ local agent: Agent
+ local act
+ local getRendererID
+ local store
+ local devtoolsUtils
+ local constants
+
+ beforeEach(function(context)
+ agent = global.agent
+ store = global.store
+
+ React = require(Packages.React)
+ jest.resetModules()
+ ReactRoblox = require(Packages.ReactRoblox)
+ devtoolsUtils = require(script.Parent.Parent.devtools.utils)
+ constants = require(script.Parent.Parent.constants)
+
+ jest.useFakeTimers()
+
+ local utils = require(script.Parent.utils)
+ act = utils.act
+ getRendererID = utils.getRendererID
+ end)
+
+ it("should not allow a root node to be collapsed", function()
+ local function Component()
+ return React.createElement("TextLabel", { Text = "Hi" })
+ end
+
+ -- ROBLOX deviation: use root:render to render instead of ReactDOM.render
+ act(function()
+ local root = ReactRoblox.createRoot(Instance.new("Frame"))
+ root:render(React.createElement(Component, {
+ count = 4,
+ }))
+ end)
+
+ jestExpect(devtoolsUtils.printStore(store)).toBe([[
+[root]
+ ]])
+ jestExpect(store:getRoots()).toHaveLength(1)
+
+ local rootID = store:getRoots()[1]
+
+ jestExpect(function()
+ return store:toggleIsCollapsed(rootID, true)
+ end).toThrow("Root nodes cannot be collapsed")
+ end)
+ it("should properly handle a root with no visible nodes", function()
+ local function Root(props)
+ return props.children
+ end
+ -- ROBLOX deviation: use root:render instead of ReactDOM.render
+ local root = ReactRoblox.createRoot(Instance.new("Frame"))
+ act(function()
+ root:render(React.createElement(Root, nil, {}))
+ end)
+
+ jestExpect(devtoolsUtils.printStore(store)).toBe([[
+[root]
+ ]])
+ act(function()
+ root:render(React.createElement("Frame"))
+ end)
+ jestExpect(devtoolsUtils.printStore(store)).toBe("[root]")
+ end)
+ describe("collapseNodesByDefault:false", function()
+ beforeEach(function()
+ store:setCollapseNodesByDefault(false)
+ end)
+ it("should support mount and update operations", function()
+ constants.__DEBUG__ = true
+ local function Child()
+ return React.createElement("TextLabel", { Text = "Hi!" })
+ end
+ local function Parent(props)
+ local count = props.count
+ return Array.map(fill(count, true), function(_, index)
+ return React.createElement(Child, { key = index })
+ end)
+ end
+ -- ROBLOX deviation: switched ordering for variable definition order
+ local function Grandparent(props)
+ local count = props.count
+ return React.createElement(
+ React.Fragment,
+ nil,
+ React.createElement(Parent, { count = count }),
+ React.createElement(Parent, { count = count })
+ )
+ end
+
+ -- ROBLOX deviation: Use Frame instance rather than DOM element
+ local root = ReactRoblox.createRoot(Instance.new("Frame"))
+ act(function()
+ return root:render(React.createElement(Grandparent, { count = 4 }))
+ end)
+ -- ROBLOX deviation: we must key children implicitly due to the Roblox DOM being unordered
+ jestExpect(devtoolsUtils.printStore(store)).toBe([[[root]
+ ▾
+ ▾
+
+
+
+
+ ▾
+
+
+