-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add first take at building a full list of all refs
- Loading branch information
1 parent
dc588bb
commit e2b3881
Showing
18 changed files
with
968 additions
and
180 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,3 +15,4 @@ benchmarks/*.brs | |
isolate-* | ||
v8*.log | ||
*.cpuprofile | ||
roku_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2024 RokuCommunity | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,8 @@ | ||
{} | ||
{ | ||
"files": [ | ||
"manifest", | ||
"source/**/*.*", | ||
"components/**/*.*", | ||
"images/**/*.*" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import "pkg:/source/roku_modules/promises/promises.brs" | ||
import "pkg:/source/reftrackerLib.bs" | ||
|
||
' typecast m as ReftrackerM | ||
interface ReftrackerM | ||
nodesByKeypath as ifAssociativeArray | ||
keypathsByNodeReftrackerId as ifAssociativeArray | ||
allNodes as roSGNode[] | ||
nodeQueue as NodeQueueItem[] | ||
top as roSGNodeReftracker | ||
end interface | ||
|
||
function init() | ||
'build several parallel lookups for nodes to simplify discoverability later | ||
|
||
' a lookup of nodes by their keypath | ||
m.nodesByKeypath = {} | ||
'a lookup of all keypaths for a node indexed by the node's reftrackerId | ||
m.keypathsByNodeReftrackerId = {} | ||
'a flat list of all nodes discovered in this run | ||
m.allNodes = [] | ||
|
||
m.top.runId = reftracker.internal.getRandomUUID() | ||
end function | ||
|
||
function discover() | ||
'seed the list of nodes with all roots (should be a good starting point) | ||
for each root in m.top.getRoots() as roSGnode[] | ||
registerNodeRef(`root:<${root.subtype()}>`, root) | ||
end for | ||
|
||
'process the nodes one-by-one | ||
promises.onThen(processNextNode(), function(result) | ||
print "done processing nodes" | ||
end function) | ||
end function | ||
|
||
'Register a reference to a node so we can process it later. This | ||
function registerNodeRef(keypath as string, node as roSGNode) | ||
reftrackerId = reftracker.internal.getReftrackerId(node) | ||
|
||
'store a reference to the node by its keypath | ||
m.nodesByKeypath[keypath] = node | ||
|
||
isNewNode = m.keypathsByNodeReftrackerId[reftrackerId] = invalid | ||
|
||
if isNewNode | ||
'build a new array to store all the keypaths for this node | ||
m.keypathsByNodeReftrackerId[reftrackerId] = [] | ||
'if this is the first time we've seen this node, store it in our list of all nodes | ||
m.allNodes.push(node) | ||
|
||
'register this node for future evaluation | ||
m.nodeQueue.push({ | ||
keypath: keypath, | ||
node: node | ||
}) | ||
end if | ||
m.keypathsByNodeReftrackerId[reftrackerId].push(keypath) | ||
end function | ||
|
||
function processNextNode() | ||
'if we have no more nodes, we are done! | ||
if m.nodeQueue.count() = 0 | ||
return promises.resolve(invalid) | ||
end if | ||
|
||
nodeQueueItem = m.nodeQueue.pop() | ||
|
||
return promises.chain(promises.resolve(true), nodeQueueItem).then(function(result, queueItem as NodeQueueItem) | ||
'if this node supports reftracker functionality, process the node's internal `m` | ||
if (nodeQueueItem.node as dynamic).reftrackerEnabled then | ||
return nodeQueueItem.node@.reftracker_internal_execute({ command: "discover", reftracker: m.top }) | ||
end if | ||
end function).then(function(result, nodeQueueItem as NodeQueueItem) | ||
'add all public fields to a list of stuff to work on | ||
for each field in nodeQueueItem.node.getFields() | ||
value = nodeQueueItem.node[field] | ||
reftracker.internal.registerWorkItem(m.top, `${nodeQueueItem.keypath}.${field}`, value) | ||
end for | ||
|
||
'now process this data (it will run async and process in chunks until all are finished) | ||
return reftracker.internal.processWorkItems(m.top) | ||
|
||
end function).then(function(result, nodeQueueItem as NodeQueueItem) | ||
|
||
return processNextNode() | ||
end function).toPromise() | ||
end function | ||
|
||
interface NodeQueueItem | ||
keypath as string | ||
node as roSGNode | ||
end interface |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<component name="Reftracker" extends="Group"> | ||
<interface> | ||
<function name="discover" /> | ||
<function name="registerNodeRef" /> | ||
<field id="runId" type="string" /> | ||
</interface> | ||
<script type="text/brightscript" uri="pkg:/components/Reftracker.bs" /> | ||
</component> |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
import "pkg:/source/roku_modules/promises/promises.brs" | ||
|
||
'typecast m as mm | ||
|
||
interface mm | ||
reftrackers as roSGNodeReftracker[] | ||
processedItems as roAssociativeArray | ||
__deviceInfo as roDeviceInfo | ||
end interface | ||
|
||
|
||
namespace reftracker | ||
|
||
' Find all references to a SceneGraph node by its ID | ||
function findNodeById(id as string) | ||
'build the reftracker | ||
tracker = createObject("roSGNode", "Reftracker") | ||
'store a reference to it so it doesn't get lost | ||
reftracker.internal.registerReftracker(tracker) | ||
'run it | ||
tracker@.discover() | ||
end function | ||
end namespace | ||
|
||
|
||
namespace reftracker.internal | ||
|
||
' This is a generic function injected into every component as a callfunc, to be a single point of contact for all commands. | ||
'@param {ExecuteOptions} options | ||
function execute(options) | ||
if options.command = "discover" | ||
registerWorkItem(options.reftracker, options.keypath + ".m", m) | ||
return processWorkItems(options.reftracker) | ||
end if | ||
end function | ||
|
||
function getWorkQueue(tracker as roSGNodeReftracker) as WorkQueueItem[] | ||
key = `__workQueue_${tracker.id}` | ||
if m[key] = invalid | ||
m[key] = [] | ||
end if | ||
return m[key] | ||
end function | ||
|
||
' register some item to be processed later | ||
function registerWorkItem(tracker as roSGNodeReftracker, keypath as string, item as dynamic) | ||
if item <> invalid | ||
workQueue = getWorkQueue(tracker) | ||
|
||
workQueue.push({ | ||
keypath: keypath | ||
item: item | ||
}) | ||
end if | ||
end function | ||
|
||
function processWorkItems(tracker as roSGNodeReftracker) as roSGNodepromises_Promise | ||
startTime = createObject("roTimeSpan") | ||
|
||
workQueue = getWorkQueue(tracker) | ||
|
||
'if we have work, and we've been running for less than 500ms, process another item | ||
while workQueue[0] <> invalid and startTime.TotalMilliseconds() < 500 | ||
item = workQueue.Shift() | ||
|
||
itemType = type(item) | ||
|
||
'if this is a node, push register it with the reftracker to be processed later | ||
if itemType = "roSGNode" | ||
tracker@.registerNodeRef(item) | ||
else if itemType = "roArray" and not isProcessed(item) | ||
'if this is an array, register each item in the array | ||
for i = 0 to (item as ifArray).Count() - 1 | ||
registerWorkItem(tracker, `${item.keypath}.${i}`, item[i]) | ||
end for | ||
else if itemType = "roAssociativeArray" and not isProcessed(item) | ||
'if this is an associative array, register each item in the array | ||
for each key in item | ||
registerWorkItem(tracker, `${item.keypath}.${key}`, item[key]) | ||
end for | ||
else | ||
'all other data types can be ignored, since they don't include references to other nodes | ||
end if | ||
end while | ||
|
||
'we have no more work, return a resolved promise | ||
if workQueue[0] = invalid | ||
return promises.resolve(true) | ||
end if | ||
|
||
'small delay, then process work again | ||
return promises.chain(reftracker.internal.delay(.01)).then(function(result, tracker) | ||
'process work items again | ||
return processWorkItems(tracker) | ||
end function, tracker).toPromise() | ||
end function | ||
|
||
function isProcessed(item as dynamic) | ||
id = getReftrackerId(item) | ||
'if we've processed this item, it'll have its id in the processedItems array | ||
return m.processedItems[id] = invalid | ||
end function | ||
|
||
'Ensure this node has a unique reftracker id so we can use it for lookups and comparisons | ||
function getReftrackerId(item as dynamic) | ||
key = "reftracker_id" | ||
itemType = type(item) | ||
if itemType = "roSGNode" and item[key] = invalid then | ||
item.addField(key, "string", reftracker.internal.getRandomUUID()) | ||
else if itemType = "roAssociativeArray" and item[key] = invalid | ||
item[key] = reftracker.internal.getRandomUUID() | ||
else if itemType = "roArray" and item[(item as roArray).Count() - 2] <> "reftracker_id" | ||
item.push("reftracker_id") | ||
item.push(reftracker.internal.getRandomUUID()) | ||
end if | ||
end function | ||
|
||
function registerReftracker(finder as roSGNodeRefTracker) | ||
if m.reftrackers = invalid | ||
m.reftrackers = [] | ||
end if | ||
m.reftrackers.push(finder) | ||
end function | ||
|
||
function getDeviceObject() as object | ||
if m.__deviceInfo <> invalid then return m.__deviceInfo | ||
m.__deviceInfo = createObject("roDeviceInfo") | ||
return m.__deviceInfo | ||
end function | ||
|
||
' Get a promise that resolves after a given duration | ||
sub delay(duration = 0.0001 as float) | ||
timer = createObject("roSGNode", "Timer") | ||
timer.duration = duration | ||
timer.repeat = false | ||
timer.id = "__delay_" + getRandomUUID() | ||
|
||
m[timer.id] = { | ||
timer: timer | ||
promise: promises.create() | ||
} | ||
|
||
timer.observeFieldScoped("fire", (sub(event as object) | ||
delayId = event.getNode() | ||
options = m[delayId] | ||
promise = options.promise | ||
promises.resolve(promise, invalid) | ||
m[delayId].unobserveFieldScoped("fire") | ||
m.delete(delayId) | ||
end sub).toStr().mid(10)) | ||
|
||
timer.control = "start" | ||
end sub | ||
|
||
function getRandomUUID() | ||
return getDeviceObject().getRandomUUID() | ||
end function | ||
|
||
function isNode(value as dynamic, subtype = "" as string) | ||
return type(value) = "roSGNode" and (subType = "" or value.isSubtype(subType)) | ||
end function | ||
|
||
interface ExecuteOptions | ||
command as string | ||
keypath as string | ||
reftracker as roSGNodeReftracker | ||
end interface | ||
|
||
interface WorkQueueItem | ||
keypath as string | ||
item as dynamic | ||
end interface | ||
end namespace |
Oops, something went wrong.