mirror of
https://github.com/Ukendio/jecs.git
synced 2026-03-18 00:44:32 +00:00
Compare commits
No commits in common. "19823453aadc321760e6252a82710a2bad9b0da4" and "fc80d631ab9351236528606d4ca0cea812f88fb1" have entirely different histories.
19823453aa
...
fc80d631ab
152 changed files with 737 additions and 20512 deletions
17
.github/workflows/publish-npm.yml
vendored
Executable file
17
.github/workflows/publish-npm.yml
vendored
Executable file
|
|
@ -0,0 +1,17 @@
|
||||||
|
name: publish-npm
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
- uses: JS-DevTools/npm-publish@v3
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.NPM_AUTH_TOKEN }}
|
||||||
44
.github/workflows/release.yaml
vendored
44
.github/workflows/release.yaml
vendored
|
|
@ -2,13 +2,7 @@ name: release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags: ["v*", "workflow_dispatch"]
|
||||||
- "v*"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
@ -35,13 +29,15 @@ jobs:
|
||||||
|
|
||||||
release:
|
release:
|
||||||
name: Release
|
name: Release
|
||||||
needs: build
|
needs: [build]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Project
|
- name: Checkout Project
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Download Build
|
- name: Download Jecs Build
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: build
|
name: build
|
||||||
|
|
@ -54,12 +50,12 @@ jobs:
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
name: Jecs ${{ github.ref_name }}
|
name: Jecs ${{ github.ref_name }}
|
||||||
tag_name: ${{ github.ref_name }}
|
files: |
|
||||||
files: jecs.rbxm
|
jecs.rbxm
|
||||||
|
|
||||||
publish-wally:
|
publish:
|
||||||
name: Publish to Wally
|
name: Publish
|
||||||
needs: release
|
needs: [release]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Project
|
- name: Checkout Project
|
||||||
|
|
@ -73,23 +69,3 @@ jobs:
|
||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: wally publish
|
run: wally publish
|
||||||
|
|
||||||
publish-npm:
|
|
||||||
name: Publish to NPM
|
|
||||||
needs: release
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout Project
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "24"
|
|
||||||
registry-url: "https://registry.npmjs.org"
|
|
||||||
|
|
||||||
- name: Install
|
|
||||||
run: npm install
|
|
||||||
|
|
||||||
- name: Publish
|
|
||||||
run: npm publish
|
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,6 @@ end
|
||||||
-- local tgt = tonumber(tokens[2]) :: jecs.Entity
|
-- local tgt = tonumber(tokens[2]) :: jecs.Entity
|
||||||
|
|
||||||
-- rel = ecs_ensure_entity(world, rel)
|
-- rel = ecs_ensure_entity(world, rel)
|
||||||
--
|
|
||||||
-- tgt = ecs_ensure_entity(world, tgt)
|
-- tgt = ecs_ensure_entity(world, tgt)
|
||||||
|
|
||||||
-- return jecs.pair(rel, tgt)
|
-- return jecs.pair(rel, tgt)
|
||||||
|
|
|
||||||
|
|
@ -169,12 +169,11 @@ local function networking_send(world: jecs.World)
|
||||||
local set_values = {} :: { any }
|
local set_values = {} :: { any }
|
||||||
local set_n = 0
|
local set_n = 0
|
||||||
local removed_n = 0
|
local removed_n = 0
|
||||||
local component_is_a_tag = jecs.is_tag(world, component)
|
|
||||||
for e, v in storage do
|
for e, v in storage do
|
||||||
if v ~= "jecs.Remove" then
|
if v ~= "jecs.Remove" then
|
||||||
set_n += 1
|
set_n += 1
|
||||||
set_ids[set_n] = e
|
set_ids[set_n] = e
|
||||||
set_values[set_n] = if component_is_a_tag then 0 else v
|
set_values[set_n] = if is_tag then 0 else v
|
||||||
elseif world:contains(e) then
|
elseif world:contains(e) then
|
||||||
removed_n += 1
|
removed_n += 1
|
||||||
removed_ids[removed_n] = e
|
removed_ids[removed_n] = e
|
||||||
|
|
|
||||||
|
|
@ -43,12 +43,6 @@ world:add(entity, Dead) -- Adds the tag
|
||||||
|
|
||||||
print(world:has(entity, Dead)) -- true
|
print(world:has(entity, Dead)) -- true
|
||||||
|
|
||||||
-- jecs.is_tag(world, id) returns true if the id is a tag (no data).
|
|
||||||
print(jecs.is_tag(world, Dead)) -- true
|
|
||||||
|
|
||||||
local Position = world:component() :: jecs.Id<number>
|
|
||||||
print(jecs.is_tag(world, Position)) -- false
|
|
||||||
|
|
||||||
-- Tags are removed using world:remove(entity, component)
|
-- Tags are removed using world:remove(entity, component)
|
||||||
|
|
||||||
world:remove(entity, Dead)
|
world:remove(entity, Dead)
|
||||||
|
|
|
||||||
|
|
@ -82,23 +82,3 @@ world:set(e1, Position, vector.create(10, 20, 30))
|
||||||
- [Position, Velocity] -> [Position] (for removing Velocity)
|
- [Position, Velocity] -> [Position] (for removing Velocity)
|
||||||
|
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local pair = jecs.pair
|
|
||||||
|
|
||||||
local Likes = world:component()
|
|
||||||
local alice = world:entity()
|
|
||||||
local bob = world:entity()
|
|
||||||
local charlie = world:entity()
|
|
||||||
|
|
||||||
local e2 = world:entity()
|
|
||||||
world:add(e2, pair(Likes, alice)) -- Creates archetype [pair(Likes, alice)]
|
|
||||||
|
|
||||||
local e3 = world:entity()
|
|
||||||
world:add(e3, pair(Likes, bob)) -- Creates archetype [pair(Likes, bob)]
|
|
||||||
|
|
||||||
local e4 = world:entity()
|
|
||||||
world:add(e3, pair(Likes, charlie)) -- Creates archetype [pair(Likes, charlie)]
|
|
||||||
world:add(e3, pair(Likes, bob)) -- Creates archetype [pair(Likes, bob), pair(Likes, charlie)]
|
|
||||||
|
|
||||||
-- Each different target creates a new archetype, leading to fragmentation
|
|
||||||
-- This is why relationships can increase archetype count significantly
|
|
||||||
|
|
@ -1,3 +1,9 @@
|
||||||
|
local jecs = require("@jecs")
|
||||||
|
local pair = jecs.pair
|
||||||
|
local world = jecs.world()
|
||||||
|
|
||||||
|
local Likes = world:component()
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
Fragmentation is a property of archetype-based ECS implementations where entities
|
Fragmentation is a property of archetype-based ECS implementations where entities
|
||||||
are spread out over more archetypes as the number of different component combinations
|
are spread out over more archetypes as the number of different component combinations
|
||||||
|
|
@ -24,3 +30,19 @@
|
||||||
pair(jecs.Wildcard, Apples) indices. For this reason, creating new archetypes
|
pair(jecs.Wildcard, Apples) indices. For this reason, creating new archetypes
|
||||||
with relationships has a higher overhead than an archetype without relationships.
|
with relationships has a higher overhead than an archetype without relationships.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
|
local alice = world:entity()
|
||||||
|
local bob = world:entity()
|
||||||
|
local charlie = world:entity()
|
||||||
|
|
||||||
|
local e1 = world:entity()
|
||||||
|
world:add(e1, pair(Likes, alice)) -- Creates archetype [pair(Likes, alice)]
|
||||||
|
|
||||||
|
local e2 = world:entity()
|
||||||
|
world:add(e2, pair(Likes, bob)) -- Creates archetype [pair(Likes, bob)]
|
||||||
|
|
||||||
|
local e3 = world:entity()
|
||||||
|
world:add(e3, pair(Likes, charlie)) -- Creates archetype [pair(Likes, charlie)]
|
||||||
|
|
||||||
|
-- Each different target creates a new archetype, leading to fragmentation
|
||||||
|
-- This is why relationships can increase archetype count significantly
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
Relationships makes it possible to describe entity graphs natively in ECS.
|
Relationships makes it possible to describe entity graphs natively in ECS.
|
||||||
|
|
||||||
Adding/removing relationships is similar to adding/removing regular components,
|
Adding/removing relationships is similar to adding/removing regular components,
|
||||||
with the difference that instead of a single component id, a relationship adds
|
with as difference that instead of a single component id, a relationship adds
|
||||||
a pair of two things to an entity. In this pair, the first element represents
|
a pair of two things to an entity. In this pair, the first element represents
|
||||||
the relationship (e.g. "Eats"), and the second element represents the relationship
|
the relationship (e.g. "Eats"), and the second element represents the relationship
|
||||||
target (e.g. "Apples").
|
target (e.g. "Apples").
|
||||||
|
|
@ -36,6 +36,9 @@ world:add(alice, pair(Likes, bob))
|
||||||
-- Test if entity has a relationship pair
|
-- Test if entity has a relationship pair
|
||||||
print(world:has(bob, pair(Eats, Apples))) -- true
|
print(world:has(bob, pair(Eats, Apples))) -- true
|
||||||
|
|
||||||
|
-- Test if entity has a relationship wildcard
|
||||||
|
print(world:has(bob, pair(Eats, jecs.Wildcard))) -- true
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
Querying for relationship targets
|
Querying for relationship targets
|
||||||
|
|
||||||
|
|
@ -74,6 +77,26 @@ for child in world:query(pair(ChildOf, parent)) do
|
||||||
print(`Entity {child} is a child of parent {parent}`)
|
print(`Entity {child} is a child of parent {parent}`)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Querying with wildcards and getting targets
|
||||||
|
|
||||||
|
When you query with a wildcard, you can use world:target() to get the
|
||||||
|
actual target entity. This is useful when you want to find all entities
|
||||||
|
with a relationship, regardless of the target.
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- Find all entities that eat something (any target)
|
||||||
|
for entity in world:query(pair(Eats, jecs.Wildcard)) do
|
||||||
|
local food = world:target(entity, Eats) -- Get the actual target
|
||||||
|
print(`Entity {entity} eats {food}`)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Find all entities that like someone (any target)
|
||||||
|
for entity in world:query(pair(Likes, jecs.Wildcard)) do
|
||||||
|
local target = world:target(entity, Likes)
|
||||||
|
print(`Entity {entity} likes {target}`)
|
||||||
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
Combining relationship queries with regular components
|
Combining relationship queries with regular components
|
||||||
|
|
||||||
|
|
@ -95,6 +118,39 @@ for entity, pos, health in world:query(Position, Health, pair(ChildOf, parent))
|
||||||
print(`Child {entity} has position {pos} and health {health}`)
|
print(`Child {entity} has position {pos} and health {health}`)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Querying for entities with multiple relationship targets
|
||||||
|
|
||||||
|
An entity can have multiple relationships with the same relationship type
|
||||||
|
but different targets. For example, bob might like both alice and charlie.
|
||||||
|
|
||||||
|
When querying with a wildcard, you'll get the entity once, but world:target()
|
||||||
|
will return the first matching target. If you need all targets, you'll need
|
||||||
|
to use a different approach (see the targets example for advanced usage).
|
||||||
|
]]
|
||||||
|
|
||||||
|
local charlie = world:entity()
|
||||||
|
world:add(bob, pair(Likes, charlie))
|
||||||
|
|
||||||
|
-- This query will return bob once, even though bob likes both alice and charlie
|
||||||
|
for entity in world:query(pair(Likes, jecs.Wildcard)) do
|
||||||
|
local target = world:target(entity, Likes)
|
||||||
|
print(`Entity {entity} likes {target}`) -- Will show one target per entity
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Querying for all relationships with a specific target
|
||||||
|
|
||||||
|
You can also query for all entities that have any relationship with a
|
||||||
|
specific target using a wildcard for the relationship part.
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- Find all entities that have any relationship with alice as the target
|
||||||
|
for entity in world:query(pair(jecs.Wildcard, alice)) do
|
||||||
|
-- Note: This is less common and may have performance implications
|
||||||
|
print(`Entity {entity} has some relationship with alice`)
|
||||||
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
Relationship pairs, just like regular component, can be associated with data.
|
Relationship pairs, just like regular component, can be associated with data.
|
||||||
]]
|
]]
|
||||||
|
|
@ -122,4 +178,30 @@ for entity, eats_data in world:query(pair(Eats, Apples)) do
|
||||||
print(`Entity {entity} eats apples: amount = {eats_data.amount}`)
|
print(`Entity {entity} eats apples: amount = {eats_data.amount}`)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- For wildcard queries and world:target (0-based index), see 042_target.luau and 043_wildcards.luau.
|
--[[
|
||||||
|
When querying for relationship pairs, it is often useful to be able to find
|
||||||
|
all instances for a given relationship or target. To accomplish this, a game
|
||||||
|
can use wildcard expressions.
|
||||||
|
|
||||||
|
Wildcards may used for the relationship or target part of a pair:
|
||||||
|
|
||||||
|
pair(Likes, jecs.Wildcard) -- Matches all Likes relationships
|
||||||
|
pair(jecs.Wildcard, Alice) -- Matches all relationships with Alice as target
|
||||||
|
|
||||||
|
Using world:target() is the recommended way to get the target in a wildcard
|
||||||
|
query. However, if you're in a very hot path and need maximum performance,
|
||||||
|
you can access the relationship column directly (see advanced examples).
|
||||||
|
]]
|
||||||
|
|
||||||
|
for entity in world:query(pair(Eats, jecs.Wildcard)) do
|
||||||
|
local nth = 0
|
||||||
|
local food = world:target(entity, Eats, nth)
|
||||||
|
while food do
|
||||||
|
local eats_data = world:get(entity, pair(Eats, food))
|
||||||
|
assert(eats_data) -- This coerces the type to be non-nilable for the type checker
|
||||||
|
print(`Entity {entity} eats {food}: amount = {eats_data.amount}`)
|
||||||
|
|
||||||
|
nth += 1
|
||||||
|
food = world:target(entity, Eats, nth)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
--[[
|
|
||||||
world:target(entity, relation, index?) returns the target of a relationship
|
|
||||||
on an entity. The index is 0-based. If no target exists at that index, it
|
|
||||||
returns nil.
|
|
||||||
|
|
||||||
Use target when you have queried with a wildcard (e.g. pair(Eats, jecs.Wildcard))
|
|
||||||
and need the actual target entity for each result. Without an index, the
|
|
||||||
default is 0 (the first target).
|
|
||||||
]]
|
|
||||||
|
|
||||||
local jecs = require("@jecs")
|
|
||||||
local pair = jecs.pair
|
|
||||||
local world = jecs.world()
|
|
||||||
|
|
||||||
local Eats = world:entity()
|
|
||||||
local Apples = world:entity()
|
|
||||||
local Oranges = world:entity()
|
|
||||||
local bob = world:entity()
|
|
||||||
|
|
||||||
world:add(bob, pair(Eats, Apples))
|
|
||||||
world:add(bob, pair(Eats, Oranges))
|
|
||||||
|
|
||||||
-- First target is at index 0
|
|
||||||
local first = world:target(bob, Eats, 0)
|
|
||||||
print(first == Apples) -- true
|
|
||||||
|
|
||||||
-- Second target is at index 1
|
|
||||||
local second = world:target(bob, Eats, 1)
|
|
||||||
print(second == Oranges) -- true
|
|
||||||
|
|
||||||
-- No third target: index 2 returns nil
|
|
||||||
local third = world:target(bob, Eats, 2)
|
|
||||||
print(third == nil) -- true
|
|
||||||
|
|
||||||
-- Omitting the index is the same as index 0
|
|
||||||
local default = world:target(bob, Eats)
|
|
||||||
print(default == Apples) -- true
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
--[[
|
|
||||||
Wildcards let you query relationships without specifying the exact target or
|
|
||||||
relationship. jecs.Wildcard matches any entity in that slot.
|
|
||||||
|
|
||||||
- pair(relation, jecs.Wildcard) matches that relationship with any target.
|
|
||||||
- pair(jecs.Wildcard, target) matches any relationship with that target.
|
|
||||||
|
|
||||||
Use world:target(entity, relation, index) to get the actual target when
|
|
||||||
querying with a wildcard. The index is 0-based (see 042_target.luau).
|
|
||||||
]]
|
|
||||||
|
|
||||||
local jecs = require("@jecs")
|
|
||||||
local pair = jecs.pair
|
|
||||||
local world = jecs.world()
|
|
||||||
|
|
||||||
local Eats = world:component() :: jecs.Id<{ amount: number }>
|
|
||||||
local Likes = world:entity()
|
|
||||||
local Apples = world:entity()
|
|
||||||
local alice = world:entity()
|
|
||||||
local bob = world:entity()
|
|
||||||
|
|
||||||
world:add(bob, pair(Eats, Apples))
|
|
||||||
world:set(bob, pair(Eats, Apples), { amount = 1 })
|
|
||||||
world:add(bob, pair(Likes, alice))
|
|
||||||
|
|
||||||
-- world:has with wildcard
|
|
||||||
print(world:has(bob, pair(Eats, jecs.Wildcard))) -- true
|
|
||||||
|
|
||||||
-- Query with wildcard: all entities that eat something
|
|
||||||
for entity in world:query(pair(Eats, jecs.Wildcard)) do
|
|
||||||
local food = world:target(entity, Eats)
|
|
||||||
print(`Entity {entity} eats {food}`)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Query with wildcard: all entities that like someone
|
|
||||||
for entity in world:query(pair(Likes, jecs.Wildcard)) do
|
|
||||||
local target = world:target(entity, Likes)
|
|
||||||
print(`Entity {entity} likes {target}`)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Multiple targets: index is 0-based. Iterate until nil.
|
|
||||||
local charlie = world:entity()
|
|
||||||
world:add(bob, pair(Likes, charlie))
|
|
||||||
|
|
||||||
for entity in world:query(pair(Likes, jecs.Wildcard)) do
|
|
||||||
local nth = 0
|
|
||||||
local target = world:target(entity, Likes, nth)
|
|
||||||
while target do
|
|
||||||
print(`Entity {entity} likes {target}`)
|
|
||||||
nth += 1
|
|
||||||
target = world:target(entity, Likes, nth)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- pair(jecs.Wildcard, target): all relationships that have this target
|
|
||||||
for entity in world:query(pair(jecs.Wildcard, alice)) do
|
|
||||||
print(`Entity {entity} has some relationship with alice`)
|
|
||||||
end
|
|
||||||
|
|
@ -70,7 +70,7 @@ end)
|
||||||
local Health = world:component()
|
local Health = world:component()
|
||||||
local Dead = world:component()
|
local Dead = world:component()
|
||||||
|
|
||||||
world:set(Health, jecs.OnRemove, function(entity: jecs.Entity, id, delete)
|
world:set(Health, jecs.OnRemove, function(entity, id, delete)
|
||||||
if delete then
|
if delete then
|
||||||
-- Entity is being deleted, don't try to clean up
|
-- Entity is being deleted, don't try to clean up
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
--[[
|
|
||||||
Signals let you subscribe to component add, change, and remove events with
|
|
||||||
multiple listeners per component. Unlike hooks (see 110_hooks.luau), which
|
|
||||||
allow only one OnAdd, OnChange, and OnRemove per component, signals support
|
|
||||||
any number of subscribers and each subscription returns an unsubscribe
|
|
||||||
function so you can clean up when you no longer need to listen.
|
|
||||||
|
|
||||||
Use signals when you need several independent systems to react to the same
|
|
||||||
component lifecycle events, or when you want to subscribe and unsubscribe
|
|
||||||
dynamically (e.g. a UI that only cares while it's mounted).
|
|
||||||
]]
|
|
||||||
|
|
||||||
local jecs = require("@jecs")
|
|
||||||
local world = jecs.world()
|
|
||||||
|
|
||||||
local Position = world:component() :: jecs.Id<{ x: number, y: number }>
|
|
||||||
|
|
||||||
--[[
|
|
||||||
world:added(component, fn)
|
|
||||||
|
|
||||||
Subscribe to "component added" events. Your callback is invoked with:
|
|
||||||
(entity, id, value, oldarchetype) whenever the component is added to an entity.
|
|
||||||
|
|
||||||
Returns a function; call it to unsubscribe.
|
|
||||||
]]
|
|
||||||
|
|
||||||
local unsub_added = world:added(Position, function(entity, id, value, oldarchetype)
|
|
||||||
print(`Position added to entity {entity}: ({value.x}, {value.y})`)
|
|
||||||
end)
|
|
||||||
|
|
||||||
--[[
|
|
||||||
world:changed(component, fn)
|
|
||||||
|
|
||||||
Subscribe to "component changed" events. Your callback is invoked with:
|
|
||||||
(entity, id, value, oldarchetype) whenever the component's value is updated
|
|
||||||
on an entity (e.g. via world:set).
|
|
||||||
|
|
||||||
Returns a function; call it to unsubscribe.
|
|
||||||
]]
|
|
||||||
|
|
||||||
local unsub_changed = world:changed(Position, function(entity, id, value, oldarchetype)
|
|
||||||
print(`Position changed on entity {entity}: ({value.x}, {value.y})`)
|
|
||||||
end)
|
|
||||||
|
|
||||||
--[[
|
|
||||||
world:removed(component, fn)
|
|
||||||
|
|
||||||
Subscribe to "component removed" events. Your callback is invoked with:
|
|
||||||
(entity, id, delete?) when the component is removed. The third argument
|
|
||||||
`delete` is true when the entity is being deleted, false or nil when
|
|
||||||
only the component was removed (same semantics as OnRemove in 110_hooks).
|
|
||||||
|
|
||||||
Returns a function; call it to unsubscribe.
|
|
||||||
]]
|
|
||||||
|
|
||||||
local unsub_removed = world:removed(Position, function(entity, id, delete)
|
|
||||||
if delete then
|
|
||||||
print(`Entity {entity} deleted (had Position)`)
|
|
||||||
else
|
|
||||||
print(`Position removed from entity {entity}`)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
local e = world:entity()
|
|
||||||
world:set(e, Position, { x = 10, y = 20 }) -- added
|
|
||||||
world:set(e, Position, { x = 30, y = 40 }) -- changed
|
|
||||||
world:remove(e, Position) -- removed
|
|
||||||
|
|
||||||
world:added(Position, function(entity)
|
|
||||||
print("Second listener: Position added")
|
|
||||||
end)
|
|
||||||
|
|
||||||
world:set(e, Position, { x = 0, y = 0 }) -- Multiple listeners are all invoked
|
|
||||||
|
|
||||||
-- Unsubscribe when you no longer need to listen
|
|
||||||
unsub_added()
|
|
||||||
unsub_changed()
|
|
||||||
unsub_removed()
|
|
||||||
|
|
||||||
world:set(e, Position, { x = 1, y = 1 })
|
|
||||||
world:remove(e, Position)
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
-- These notes are my thoughts jotted down from having experienced these
|
|
||||||
-- problems myself and gathered insights from many admired individuals such as
|
|
||||||
-- Sander Mertens, Ryan Fleury, Jonathon Blow, Benjamin Saunders and many more...
|
|
||||||
|
|
||||||
--[[
|
|
||||||
|
|
||||||
In 1993, the original source code for DOOM was about 50,000 lines of code. And
|
|
||||||
when your code gets into that neighbourhood, it should provide a large amount of
|
|
||||||
interesting and novel functionality. If it doesn't, perhaps it is time to ask questions.
|
|
||||||
|
|
||||||
Please, try to write code that is small, and that does a lot for its size.
|
|
||||||
|
|
||||||
Please, after you finish writing something, ask yourself whether you are
|
|
||||||
satisfied with how robust it is, and with how much it gets done for how much
|
|
||||||
code there is.
|
|
||||||
|
|
||||||
- Transfer of tacit knowledge is incredibly important. If tacit knowledge
|
|
||||||
about the code base is lost, ability to work on it at the same level of quality
|
|
||||||
is lost. Over time code quality will decline as code size grows.
|
|
||||||
|
|
||||||
- Tacit knowledge is very hard to recover by looking at a maze of code,
|
|
||||||
and it takes a long time to do so.
|
|
||||||
|
|
||||||
- You will often hear that "every semantic distinction deserves its own
|
|
||||||
component or tag". Sometimes this is correct. A well chosen component boundary
|
|
||||||
can make queries clear and systems obvious. But sometimes this distinction would
|
|
||||||
be better served as a field, a bitset, or a local data structure. The
|
|
||||||
representation should match the problem.
|
|
||||||
|
|
||||||
Sub-Essay Here: Code Should Not Try To Meet Every Need Anyone May Ever Have.
|
|
||||||
|
|
||||||
Over-generalization leads to bloat and poor functionality. A common failure mode
|
|
||||||
is writing code "for everyone" while building configuration for every scenario,
|
|
||||||
abstractions for every future feature, and extension points for every imagined
|
|
||||||
consumer. It sounds responsible. It usually isn't.
|
|
||||||
Specialization is good in many cases. Think about the guy with the truck full of
|
|
||||||
automotive tools, who has a bunch of different wrenches. He doesn't carry one
|
|
||||||
wrench that transforms into every tool; he carries a few specialized tools that
|
|
||||||
are reliable and fast to use. One reason we have endless bloat is that we teach
|
|
||||||
that all code should expand until it meets all needs. This is wrong,
|
|
||||||
empirically, and we should stop teaching it.
|
|
||||||
|
|
||||||
- Relationships are very powerful however, with each pair being an unique
|
|
||||||
component, it can be an easy way to accidentally increase the number of archetypes which can cause
|
|
||||||
higher churn in systems.
|
|
||||||
|
|
||||||
- A hook is not a replacement for systems. They are for enforcing invariants when data changes during different lifecycles.
|
|
||||||
When gameplay logic that should run predictably each frame is instead scattered
|
|
||||||
across hooks, behaviour becomes implicit when it is triggered indirectly through
|
|
||||||
a cascade of changes that, logic split across many small
|
|
||||||
callbacks that fire in surprising order. Which also gets harder to reason about
|
|
||||||
and optimize.
|
|
||||||
|
|
||||||
]]
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
local RunService = game:GetService("RunService")
|
|
||||||
|
|
||||||
local Input = require("@modules/Input/module")
|
|
||||||
|
|
||||||
local function cameraSystem()
|
|
||||||
local look = Input.value2d("look")
|
|
||||||
|
|
||||||
-- rotate camera with look
|
|
||||||
end
|
|
||||||
|
|
||||||
local function characterMovement()
|
|
||||||
local move = Input.clamped2d("move")
|
|
||||||
|
|
||||||
-- humanoid:Move(move)
|
|
||||||
|
|
||||||
if Input.justPressed("jump") then
|
|
||||||
-- humanoid.Jump = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
RunService.RenderStepped:Connect(function(deltaTime)
|
|
||||||
Input.update(deltaTime)
|
|
||||||
|
|
||||||
Input.runPhase("RenderStepped", function()
|
|
||||||
cameraSystem()
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
RunService.PreSimulation:Connect(function(deltaTime)
|
|
||||||
Input.update(deltaTime)
|
|
||||||
|
|
||||||
Input.runPhase("PreSimulation", function()
|
|
||||||
characterMovement()
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
@ -1,269 +0,0 @@
|
||||||
local UserInputService = game:GetService("UserInputService")
|
|
||||||
local UserGameSettings = UserSettings():GetService("UserGameSettings")
|
|
||||||
|
|
||||||
-- Phase 1: Collect raw inputs from Roblox APIs. Here we are using UserInputService but you could use the other APIs.
|
|
||||||
local rawInput = {
|
|
||||||
space = false,
|
|
||||||
w = false,
|
|
||||||
a = false,
|
|
||||||
s = false,
|
|
||||||
d = false,
|
|
||||||
buttonA = false,
|
|
||||||
mouseDelta = Vector2.zero,
|
|
||||||
leftThumbstickDelta = Vector2.zero,
|
|
||||||
rightThumbstickDelta = Vector2.zero,
|
|
||||||
}
|
|
||||||
|
|
||||||
UserInputService.InputBegan:Connect(function(input, sink)
|
|
||||||
if sink then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if input.KeyCode == Enum.KeyCode.W then
|
|
||||||
rawInput.w = true
|
|
||||||
elseif input.KeyCode == Enum.KeyCode.A then
|
|
||||||
rawInput.a = true
|
|
||||||
elseif input.KeyCode == Enum.KeyCode.S then
|
|
||||||
rawInput.s = true
|
|
||||||
elseif input.KeyCode == Enum.KeyCode.D then
|
|
||||||
rawInput.d = true
|
|
||||||
elseif input.KeyCode == Enum.KeyCode.Space then
|
|
||||||
rawInput.space = true
|
|
||||||
elseif input.KeyCode == Enum.KeyCode.ButtonA then
|
|
||||||
rawInput.buttonA = true
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
UserInputService.InputChanged:Connect(function(input, sink)
|
|
||||||
if input.UserInputType == Enum.UserInputType.MouseMovement then
|
|
||||||
rawInput.mouseDelta = Vector2.new(input.Delta.X, -input.Delta.Y)
|
|
||||||
elseif input.KeyCode == Enum.KeyCode.Thumbstick1 then
|
|
||||||
rawInput.leftThumbstickDelta = Vector2.new(input.Position.X, input.Position.Y)
|
|
||||||
elseif input.KeyCode == Enum.KeyCode.Thumbstick2 then
|
|
||||||
rawInput.rightThumbstickDelta = Vector2.new(input.Position.X, input.Position.Y)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
UserInputService.InputEnded:Connect(function(input, sink)
|
|
||||||
if input.KeyCode == Enum.KeyCode.W then
|
|
||||||
rawInput.w = false
|
|
||||||
elseif input.KeyCode == Enum.KeyCode.A then
|
|
||||||
rawInput.a = false
|
|
||||||
elseif input.KeyCode == Enum.KeyCode.S then
|
|
||||||
rawInput.s = false
|
|
||||||
elseif input.KeyCode == Enum.KeyCode.D then
|
|
||||||
rawInput.d = false
|
|
||||||
elseif input.KeyCode == Enum.KeyCode.Space then
|
|
||||||
rawInput.space = false
|
|
||||||
elseif input.KeyCode == Enum.KeyCode.ButtonA then
|
|
||||||
rawInput.buttonA = false
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- Phase 2: Derive action state from raw inputs.
|
|
||||||
|
|
||||||
local SENSITIVITY_MOUSE = Vector2.new(1, 0.77) * math.rad(0.5)
|
|
||||||
local SENSITIVITY_GAMEPAD = Vector2.new(1, 0.77) * math.rad(4) * 60
|
|
||||||
|
|
||||||
local function virtualVector2(up: boolean, down: boolean, left: boolean, right: boolean): Vector2
|
|
||||||
local x = 0
|
|
||||||
local y = 0
|
|
||||||
if up then
|
|
||||||
y += 1
|
|
||||||
end
|
|
||||||
if down then
|
|
||||||
y -= 1
|
|
||||||
end
|
|
||||||
if left then
|
|
||||||
x -= 1
|
|
||||||
end
|
|
||||||
if right then
|
|
||||||
x += 1
|
|
||||||
end
|
|
||||||
|
|
||||||
return Vector2.new(x, y)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function scaledDeadZone(value: number, lowerThreshold: number): number
|
|
||||||
local lowerBound = math.max(math.abs(value) - lowerThreshold, 0)
|
|
||||||
local scaledValue = lowerBound / (1 - lowerThreshold)
|
|
||||||
|
|
||||||
return math.min(scaledValue, 1) * math.sign(value)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function radialDeadZone(value: Vector2, threshold: number): Vector2
|
|
||||||
local magnitude = value.Magnitude
|
|
||||||
if magnitude == 0 then
|
|
||||||
return Vector2.zero
|
|
||||||
else
|
|
||||||
return value.Unit * scaledDeadZone(magnitude, threshold)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Convert raw inputs into action state. This function will apply modifiers like dead zones or sensitivity multipliers.
|
|
||||||
local function deriveActionState(deltaTime: number)
|
|
||||||
local keyboardMove = virtualVector2(rawInput.w, rawInput.s, rawInput.a, rawInput.d)
|
|
||||||
local gamepadMove = radialDeadZone(rawInput.leftThumbstickDelta, 0.2)
|
|
||||||
|
|
||||||
local mouseLook = rawInput.mouseDelta * SENSITIVITY_MOUSE
|
|
||||||
local gamepadLook = radialDeadZone(rawInput.rightThumbstickDelta, 0.2)
|
|
||||||
* UserGameSettings.GamepadCameraSensitivity
|
|
||||||
* SENSITIVITY_GAMEPAD
|
|
||||||
* deltaTime
|
|
||||||
|
|
||||||
return {
|
|
||||||
boolean = {
|
|
||||||
jump = rawInput.space or rawInput.buttonA,
|
|
||||||
},
|
|
||||||
value2d = {
|
|
||||||
move = keyboardMove + gamepadMove,
|
|
||||||
look = mouseLook + gamepadLook,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
-- UserGameSettings.GamepadCameraSensitivity is only updated if this is called.
|
|
||||||
UserGameSettings:SetGamepadCameraSensitivityVisible()
|
|
||||||
|
|
||||||
-- 3. The API
|
|
||||||
local ACTIONS_BOOLEAN = {
|
|
||||||
jump = true,
|
|
||||||
}
|
|
||||||
|
|
||||||
local ACTIONS_2D = {
|
|
||||||
move = true,
|
|
||||||
look = true,
|
|
||||||
}
|
|
||||||
|
|
||||||
local DEFAULT_PHASE_STATE = {
|
|
||||||
boolean = {},
|
|
||||||
justPressedCounts = {} :: { [string]: number },
|
|
||||||
justReleasedCounts = {} :: { [string]: number },
|
|
||||||
value2d = {},
|
|
||||||
}
|
|
||||||
for action in ACTIONS_BOOLEAN :: any do
|
|
||||||
DEFAULT_PHASE_STATE.boolean[action] = false
|
|
||||||
end
|
|
||||||
for action in ACTIONS_2D :: any do
|
|
||||||
DEFAULT_PHASE_STATE.value2d[action] = Vector2.zero
|
|
||||||
end
|
|
||||||
|
|
||||||
local function copyDeep<T>(value: T): T
|
|
||||||
if typeof(value) == "table" then
|
|
||||||
local clone = table.clone(value) :: any
|
|
||||||
|
|
||||||
for key, value in clone do
|
|
||||||
clone[key] = copyDeep(value)
|
|
||||||
end
|
|
||||||
|
|
||||||
return clone
|
|
||||||
else
|
|
||||||
return value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local lastInputState = deriveActionState(0)
|
|
||||||
local currentPhase = DEFAULT_PHASE_STATE
|
|
||||||
local phases = {}
|
|
||||||
|
|
||||||
local Input = {}
|
|
||||||
|
|
||||||
function Input.justPressed(action: keyof<typeof(ACTIONS_BOOLEAN)>): boolean
|
|
||||||
return currentPhase.justPressedCounts[action] ~= nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function Input.justReleased(action: keyof<typeof(ACTIONS_BOOLEAN)>): boolean
|
|
||||||
return currentPhase.justReleasedCounts[action] ~= nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function Input.pressed(action: keyof<typeof(ACTIONS_BOOLEAN)>): boolean
|
|
||||||
return currentPhase.boolean[action]
|
|
||||||
end
|
|
||||||
|
|
||||||
function Input.released(action: keyof<typeof(ACTIONS_BOOLEAN)>): boolean
|
|
||||||
return not currentPhase.boolean[action]
|
|
||||||
end
|
|
||||||
|
|
||||||
function Input.value2d(action: keyof<typeof(ACTIONS_2D)>): Vector2
|
|
||||||
return currentPhase.value2d[action]
|
|
||||||
end
|
|
||||||
|
|
||||||
function Input.unit2d(action: keyof<typeof(ACTIONS_2D)>): Vector2
|
|
||||||
local value = currentPhase.value2d[action]
|
|
||||||
if value.Magnitude > 0 then
|
|
||||||
return value.Unit
|
|
||||||
end
|
|
||||||
|
|
||||||
return value
|
|
||||||
end
|
|
||||||
|
|
||||||
function Input.clamped2d(action: keyof<typeof(ACTIONS_2D)>): Vector2
|
|
||||||
local value = currentPhase.value2d[action]
|
|
||||||
if value.Magnitude > 1 then
|
|
||||||
return value.Unit
|
|
||||||
end
|
|
||||||
|
|
||||||
return value
|
|
||||||
end
|
|
||||||
|
|
||||||
function Input.runPhase(name: string, callback: () -> ())
|
|
||||||
if not phases[name] then
|
|
||||||
phases[name] = copyDeep(DEFAULT_PHASE_STATE)
|
|
||||||
end
|
|
||||||
|
|
||||||
currentPhase = phases[name]
|
|
||||||
|
|
||||||
callback()
|
|
||||||
|
|
||||||
table.clear(currentPhase.justPressedCounts)
|
|
||||||
table.clear(currentPhase.justReleasedCounts)
|
|
||||||
|
|
||||||
for action in currentPhase.boolean do
|
|
||||||
currentPhase.boolean[action] = false
|
|
||||||
end
|
|
||||||
|
|
||||||
for action in currentPhase.value2d do
|
|
||||||
currentPhase.value2d[action] = Vector2.zero
|
|
||||||
end
|
|
||||||
|
|
||||||
currentPhase = DEFAULT_PHASE_STATE
|
|
||||||
end
|
|
||||||
|
|
||||||
function Input.update(deltaTime: number)
|
|
||||||
local inputState = deriveActionState(deltaTime)
|
|
||||||
|
|
||||||
local presses = {}
|
|
||||||
local releases = {}
|
|
||||||
for action, value in inputState.boolean do
|
|
||||||
if value and not lastInputState.boolean[action] then
|
|
||||||
table.insert(presses, action)
|
|
||||||
elseif not value and lastInputState.boolean[action] then
|
|
||||||
table.insert(releases, action)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, phase in phases do
|
|
||||||
for _, action in presses do
|
|
||||||
phase.justPressedCounts[action] = (phase.justPressedCounts[action] or 0) + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, action in releases do
|
|
||||||
phase.justReleasedCounts[action] = (phase.justReleasedCounts[action] or 0) + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
for action, value in inputState.boolean do
|
|
||||||
phase.boolean[action] = phase.boolean[action] or value
|
|
||||||
end
|
|
||||||
|
|
||||||
for action, value in inputState.value2d do
|
|
||||||
phase.value2d[action] += value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
lastInputState = inputState
|
|
||||||
|
|
||||||
-- Reset the mouse delta.
|
|
||||||
rawInput.mouseDelta = Vector2.zero
|
|
||||||
end
|
|
||||||
|
|
||||||
return Input
|
|
||||||
|
|
@ -1,106 +0,0 @@
|
||||||
local ui = require(script.Parent.Parent.Parent.Parent.ui)
|
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local query_parser = require(script.Parent.Parent.Parent.Parent.server.query_parser)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local source = vide.source
|
|
||||||
local show = vide.show
|
|
||||||
|
|
||||||
type props = {
|
|
||||||
changes: vide.Source<{[string]: string}>,
|
|
||||||
editing: vide.Source<false | string>,
|
|
||||||
adding: vide.Source<false | string>,
|
|
||||||
text: vide.Source<string>
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
|
|
||||||
local component_edit_text = source("")
|
|
||||||
local adding = props.adding
|
|
||||||
local editing = props.editing
|
|
||||||
local text = props.text
|
|
||||||
|
|
||||||
return create "Folder" {
|
|
||||||
Name = "Add Component",
|
|
||||||
|
|
||||||
show(adding, function()
|
|
||||||
return create "Frame" {
|
|
||||||
ZIndex = 1000,
|
|
||||||
Size = UDim2.new(1,16, 1, 16),
|
|
||||||
Position = UDim2.fromScale(0.5, 0.5),
|
|
||||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
||||||
|
|
||||||
BackgroundColor3 = Color3.new(0, 0, 0),
|
|
||||||
BackgroundTransparency = 0.5,
|
|
||||||
|
|
||||||
Active = true,
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
|
||||||
Padding = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.padding {
|
|
||||||
padding = UDim.new(0, 32)
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.textfield {
|
|
||||||
size = UDim2.fromOffset(200, 30),
|
|
||||||
|
|
||||||
placeholder = "Entity",
|
|
||||||
|
|
||||||
oninput = component_edit_text,
|
|
||||||
},
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
Size = UDim2.new(1, 0, 0, 30),
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
AutomaticSize = Enum.AutomaticSize.Y,
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
HorizontalFlex = Enum.UIFlexAlignment.Fill,
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
Padding = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.button {
|
|
||||||
size = UDim2.fromOffset(150, 30),
|
|
||||||
text = "Edit",
|
|
||||||
activated = function()
|
|
||||||
adding(false)
|
|
||||||
editing(component_edit_text())
|
|
||||||
end,
|
|
||||||
disabled = function()
|
|
||||||
local ok, node = pcall(query_parser, component_edit_text())
|
|
||||||
|
|
||||||
if not ok then return true end
|
|
||||||
if not node[1] then return true end
|
|
||||||
if node[2] then return true end
|
|
||||||
local n = node[1]
|
|
||||||
if n.type == "Relationship" then
|
|
||||||
if n.left.type == "Wildcard" then return true end
|
|
||||||
if n.right.type == "Wildcard" then return true end
|
|
||||||
end
|
|
||||||
|
|
||||||
return false
|
|
||||||
end,
|
|
||||||
accent = true
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.button {
|
|
||||||
size = UDim2.fromOffset(150, 30),
|
|
||||||
text = "Cancel",
|
|
||||||
activated = function()
|
|
||||||
adding(false)
|
|
||||||
end
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,118 +0,0 @@
|
||||||
local ui = require(script.Parent.Parent.Parent.Parent.ui)
|
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local show = vide.show
|
|
||||||
|
|
||||||
type props = {
|
|
||||||
components: () -> {[string]: string},
|
|
||||||
changes: vide.Source<{[string]: string}>,
|
|
||||||
editing: vide.Source<false | string>,
|
|
||||||
text: vide.Source<string>
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
|
|
||||||
local editing = props.editing
|
|
||||||
local text = props.text
|
|
||||||
local changes = props.changes
|
|
||||||
|
|
||||||
return create "Folder" {
|
|
||||||
Name = "Text Editor",
|
|
||||||
|
|
||||||
show(function()
|
|
||||||
return editing()
|
|
||||||
end, function()
|
|
||||||
return create "Frame" {
|
|
||||||
ZIndex = 1000,
|
|
||||||
Size = UDim2.new(1,16, 1, 16),
|
|
||||||
Position = UDim2.fromScale(0.5, 0.5),
|
|
||||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
||||||
|
|
||||||
BackgroundColor3 = Color3.new(0, 0, 0),
|
|
||||||
BackgroundTransparency = 0.5,
|
|
||||||
|
|
||||||
Active = true,
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
|
||||||
VerticalFlex = Enum.UIFlexAlignment.SpaceEvenly,
|
|
||||||
Padding = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.padding {
|
|
||||||
padding = UDim.new(0, 32)
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.typography {
|
|
||||||
text = function()
|
|
||||||
return `Editing {editing()}`
|
|
||||||
end
|
|
||||||
},
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
Size = UDim2.fromScale(1, 0),
|
|
||||||
|
|
||||||
create "UIFlexItem" {
|
|
||||||
FlexMode = Enum.UIFlexMode.Fill
|
|
||||||
},
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
ui.textfield {
|
|
||||||
size = UDim2.fromScale(1, 1),
|
|
||||||
position = UDim2.fromScale(0.5, 0.5),
|
|
||||||
anchorpoint = Vector2.new(0.5, 0.5),
|
|
||||||
|
|
||||||
multiline = true,
|
|
||||||
code = true,
|
|
||||||
|
|
||||||
text = text,
|
|
||||||
oninput = text,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
Size = UDim2.new(1, 0, 0, 30),
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
AutomaticSize = Enum.AutomaticSize.Y,
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
HorizontalFlex = Enum.UIFlexAlignment.Fill,
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
Padding = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.button {
|
|
||||||
size = UDim2.fromOffset(150, 30),
|
|
||||||
text = "Save Changes",
|
|
||||||
activated = function()
|
|
||||||
local key = editing()
|
|
||||||
changes()[key] = text()
|
|
||||||
|
|
||||||
editing(false)
|
|
||||||
changes(changes())
|
|
||||||
|
|
||||||
if props.components()[key] ~= nil then return end
|
|
||||||
props.components()[key] = text()
|
|
||||||
end,
|
|
||||||
accent = true
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.button {
|
|
||||||
size = UDim2.fromOffset(150, 30),
|
|
||||||
text = "Cancel Changes",
|
|
||||||
activated = function()
|
|
||||||
editing(false)
|
|
||||||
end
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
local RunService = game:GetService("RunService")
|
|
||||||
|
|
||||||
local vide = require(script.Parent.Parent.Parent.vide)
|
|
||||||
local loop = require(script.Parent.Parent.Parent.modules.loop)
|
|
||||||
local widget = require(script.widget)
|
|
||||||
|
|
||||||
local source = vide.source
|
|
||||||
local cleanup = vide.cleanup
|
|
||||||
|
|
||||||
local overview_entity = {
|
|
||||||
class_name = "app" :: "app",
|
|
||||||
name = "Entity"
|
|
||||||
}
|
|
||||||
|
|
||||||
type props = {
|
|
||||||
host: Player | "server",
|
|
||||||
vm: number,
|
|
||||||
id: number,
|
|
||||||
entity: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
local function generate_random_query_id()
|
|
||||||
return math.random(2 ^ 31 - 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
function overview_entity.mount(props: props, destroy: () -> ())
|
|
||||||
|
|
||||||
local keys = source({})
|
|
||||||
local changes = source({})
|
|
||||||
local enable_live_updates = source(true)
|
|
||||||
local apply_changes = source(false)
|
|
||||||
local deleting = source(false)
|
|
||||||
local inspect_id = generate_random_query_id()
|
|
||||||
|
|
||||||
-- check if the query and columns are properly
|
|
||||||
local app_loop = loop (
|
|
||||||
"app-client-entity",
|
|
||||||
{
|
|
||||||
host = props.host,
|
|
||||||
vm = props.vm,
|
|
||||||
id = props.id,
|
|
||||||
inspect_id = inspect_id,
|
|
||||||
entity = tonumber(props.entity),
|
|
||||||
|
|
||||||
keys = keys,
|
|
||||||
live_updates = enable_live_updates,
|
|
||||||
changes = changes,
|
|
||||||
apply_changes = apply_changes,
|
|
||||||
deleting = deleting
|
|
||||||
},
|
|
||||||
|
|
||||||
{i = 1},
|
|
||||||
script.systems.obtain_entity_data
|
|
||||||
)
|
|
||||||
|
|
||||||
cleanup(
|
|
||||||
RunService.Heartbeat:Connect(app_loop)
|
|
||||||
)
|
|
||||||
|
|
||||||
return widget {
|
|
||||||
host = props.host,
|
|
||||||
vm = props.vm,
|
|
||||||
id = props.id,
|
|
||||||
inspect_id = inspect_id,
|
|
||||||
entity = props.entity,
|
|
||||||
|
|
||||||
components = keys,
|
|
||||||
live_updates = enable_live_updates,
|
|
||||||
changes = changes,
|
|
||||||
apply_changes = apply_changes,
|
|
||||||
|
|
||||||
delete = function()
|
|
||||||
deleting(true)
|
|
||||||
end,
|
|
||||||
|
|
||||||
destroy = destroy
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
return overview_entity
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local queue = require(script.Parent.Parent.Parent.Parent.Parent.modules.queue)
|
|
||||||
local remotes = require(script.Parent.Parent.Parent.Parent.Parent.modules.remotes)
|
|
||||||
|
|
||||||
local effect = vide.effect
|
|
||||||
local cleanup = vide.cleanup
|
|
||||||
local batch = vide.batch
|
|
||||||
|
|
||||||
type Context = {
|
|
||||||
host: Player | "server",
|
|
||||||
vm: number,
|
|
||||||
id: number,
|
|
||||||
inspect_id: number,
|
|
||||||
entity: number,
|
|
||||||
|
|
||||||
keys: vide.Source<{[string]: string}>,
|
|
||||||
changes: vide.Source<{[string]: string}>,
|
|
||||||
apply_changes: vide.Source<boolean>,
|
|
||||||
live_updates: () -> boolean,
|
|
||||||
deleting: () -> boolean,
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(context: Context)
|
|
||||||
|
|
||||||
local inspect_entity_update = queue(remotes.inspect_entity_update)
|
|
||||||
|
|
||||||
local current_inspectid = context.inspect_id
|
|
||||||
local outgoing = {
|
|
||||||
host = context.host,
|
|
||||||
to_vm = context.vm,
|
|
||||||
}
|
|
||||||
|
|
||||||
remotes.inspect_entity:fire(
|
|
||||||
outgoing,
|
|
||||||
context.id,
|
|
||||||
context.entity,
|
|
||||||
current_inspectid
|
|
||||||
)
|
|
||||||
|
|
||||||
local settings_changed = false
|
|
||||||
|
|
||||||
effect(function()
|
|
||||||
context.live_updates()
|
|
||||||
settings_changed = true
|
|
||||||
end)
|
|
||||||
|
|
||||||
cleanup(function()
|
|
||||||
remotes.stop_inspect_entity:fire(
|
|
||||||
outgoing,
|
|
||||||
current_inspectid
|
|
||||||
)
|
|
||||||
end)
|
|
||||||
|
|
||||||
return function()
|
|
||||||
|
|
||||||
if context.apply_changes() then
|
|
||||||
remotes.update_entity:fire(outgoing, current_inspectid, context.changes())
|
|
||||||
context.apply_changes(false)
|
|
||||||
context.changes({})
|
|
||||||
end
|
|
||||||
|
|
||||||
if context.deleting() then
|
|
||||||
remotes.delete_entity:fire(outgoing, current_inspectid)
|
|
||||||
end
|
|
||||||
|
|
||||||
if settings_changed then
|
|
||||||
remotes.update_inspect_settings:fire(
|
|
||||||
outgoing,
|
|
||||||
current_inspectid,
|
|
||||||
{paused = not context.live_updates()}
|
|
||||||
)
|
|
||||||
settings_changed = false
|
|
||||||
end
|
|
||||||
|
|
||||||
batch(function()
|
|
||||||
for incoming, inspectid, key, value in inspect_entity_update:iter() do
|
|
||||||
if inspectid ~= current_inspectid then continue end
|
|
||||||
context.keys()[key] = value
|
|
||||||
context.keys(context.keys())
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,414 +0,0 @@
|
||||||
local RunService = game:GetService("RunService")
|
|
||||||
local UserInputService = game:GetService("UserInputService")
|
|
||||||
|
|
||||||
local ui = require(script.Parent.Parent.Parent.Parent.ui)
|
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local remotes = require(script.Parent.Parent.Parent.Parent.modules.remotes)
|
|
||||||
local add_component = require(script.Parent.add_component)
|
|
||||||
local editor = require(script.Parent.editor)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local indexes = vide.indexes
|
|
||||||
local source = vide.source
|
|
||||||
local show = vide.show
|
|
||||||
|
|
||||||
type SystemId = number
|
|
||||||
|
|
||||||
type props = {
|
|
||||||
host: Player | "server",
|
|
||||||
vm: number,
|
|
||||||
id: number,
|
|
||||||
entity: number,
|
|
||||||
inspect_id: number,
|
|
||||||
|
|
||||||
components: vide.Source<{[string]: string}>,
|
|
||||||
changes: vide.Source<{[string]: string}>,
|
|
||||||
live_updates: vide.Source<boolean>,
|
|
||||||
apply_changes: vide.Source<boolean>,
|
|
||||||
|
|
||||||
destroy: () -> (),
|
|
||||||
delete: () -> ()
|
|
||||||
}
|
|
||||||
|
|
||||||
local mouse_location = source(Vector2.zero)
|
|
||||||
|
|
||||||
RunService.PreRender:Connect(function()
|
|
||||||
mouse_location(UserInputService:GetMouseLocation())
|
|
||||||
end)
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
local outgoing = {
|
|
||||||
host = props.host,
|
|
||||||
to_vm = props.vm,
|
|
||||||
}
|
|
||||||
|
|
||||||
local live_updates = props.live_updates
|
|
||||||
local changes = props.changes
|
|
||||||
|
|
||||||
local text = source("")
|
|
||||||
local adding = source(false)
|
|
||||||
local editing = source(false :: false | string)
|
|
||||||
|
|
||||||
local function components()
|
|
||||||
local components = {}
|
|
||||||
|
|
||||||
for key, value in props.components() do
|
|
||||||
if value == "tag" then continue end
|
|
||||||
components[key] = value
|
|
||||||
end
|
|
||||||
|
|
||||||
return components
|
|
||||||
end
|
|
||||||
|
|
||||||
local function tags()
|
|
||||||
local tags = {}
|
|
||||||
|
|
||||||
for key, value in props.components() do
|
|
||||||
if value ~= "tag" then continue end
|
|
||||||
tags[key] = value
|
|
||||||
end
|
|
||||||
|
|
||||||
return tags
|
|
||||||
end
|
|
||||||
|
|
||||||
local function is_removed(value: string)
|
|
||||||
return value == "nil"
|
|
||||||
end
|
|
||||||
|
|
||||||
local function edit_component(component: string)
|
|
||||||
remotes.get_component:fire(outgoing, props.inspect_id, component)
|
|
||||||
editing(component)
|
|
||||||
|
|
||||||
text("waiting...")
|
|
||||||
end
|
|
||||||
|
|
||||||
vide.cleanup(remotes.return_component:connect(function(from, inspect, component, value)
|
|
||||||
if props.host ~= from.host then return end
|
|
||||||
if props.inspect_id ~= inspect then return end
|
|
||||||
|
|
||||||
local current_value = props.changes()[component]
|
|
||||||
|
|
||||||
if current_value then
|
|
||||||
text(current_value)
|
|
||||||
else
|
|
||||||
text(value)
|
|
||||||
end
|
|
||||||
|
|
||||||
end))
|
|
||||||
|
|
||||||
return ui.widget {
|
|
||||||
title = `Entity #{props.entity}`,
|
|
||||||
subtitle = `host: {props.host} vm: {props.vm} id: {props.id}`,
|
|
||||||
|
|
||||||
min_size = Vector2.new(300, 300),
|
|
||||||
|
|
||||||
bind_to_close = props.destroy,
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
Size = UDim2.fromScale(1, 1),
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
VerticalFlex = Enum.UIFlexAlignment.SpaceEvenly,
|
|
||||||
Padding = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
|
|
||||||
editor {
|
|
||||||
components = props.components,
|
|
||||||
editing = editing,
|
|
||||||
text = text,
|
|
||||||
changes = changes
|
|
||||||
},
|
|
||||||
|
|
||||||
add_component {
|
|
||||||
editing = edit_component,
|
|
||||||
text = text,
|
|
||||||
changes = changes,
|
|
||||||
adding = adding
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.row {
|
|
||||||
|
|
||||||
justifycontent = Enum.UIFlexAlignment.Fill,
|
|
||||||
|
|
||||||
ui.button {
|
|
||||||
text = "Live Updates",
|
|
||||||
activated = function()
|
|
||||||
live_updates(not live_updates())
|
|
||||||
end,
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
|
||||||
HorizontalFlex = Enum.UIFlexAlignment.SpaceBetween,
|
|
||||||
Padding = UDim.new(0, 4)
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.checkbox {
|
|
||||||
size = UDim2.fromOffset(16, 16),
|
|
||||||
layoutorder = -1,
|
|
||||||
checked = live_updates,
|
|
||||||
|
|
||||||
create "UIFlexItem" {
|
|
||||||
FlexMode = Enum.UIFlexMode.None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.button {
|
|
||||||
size = UDim2.fromOffset(130, 30),
|
|
||||||
text = function()
|
|
||||||
local total = 0
|
|
||||||
|
|
||||||
for _, change in changes() do
|
|
||||||
total += 1
|
|
||||||
end
|
|
||||||
|
|
||||||
return `Apply {total} Edits`
|
|
||||||
end,
|
|
||||||
disabled = function()
|
|
||||||
return next(changes()) == nil
|
|
||||||
end,
|
|
||||||
activated = function()
|
|
||||||
props.apply_changes(true)
|
|
||||||
end
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.button {
|
|
||||||
create "UIFlexItem" {
|
|
||||||
ItemLineAlignment = Enum.ItemLineAlignment.End,
|
|
||||||
},
|
|
||||||
size = UDim2.fromOffset(130, 30),
|
|
||||||
|
|
||||||
text = "Cancel changes",
|
|
||||||
disabled = function()
|
|
||||||
return next(changes()) == nil
|
|
||||||
end,
|
|
||||||
activated = function()
|
|
||||||
changes({})
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
create "ScrollingFrame" {
|
|
||||||
Size = UDim2.fromScale(1, 0),
|
|
||||||
CanvasSize = UDim2.new(),
|
|
||||||
AutomaticCanvasSize = Enum.AutomaticSize.Y,
|
|
||||||
|
|
||||||
BackgroundColor3 = ui.theme.bg[-1],
|
|
||||||
|
|
||||||
ScrollBarThickness = 6,
|
|
||||||
VerticalScrollBarInset = Enum.ScrollBarInset.Always,
|
|
||||||
|
|
||||||
create "UIFlexItem" {
|
|
||||||
FlexMode = Enum.UIFlexMode.Fill
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
Padding = UDim.new(0, 4)
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.typography {text = "Components"},
|
|
||||||
|
|
||||||
ui.container {
|
|
||||||
Size = UDim2.fromScale(1, 0),
|
|
||||||
AutomaticSize = Enum.AutomaticSize.Y,
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
SortOrder = Enum.SortOrder.Name,
|
|
||||||
},
|
|
||||||
|
|
||||||
indexes(components, function(value, key)
|
|
||||||
return ui.button {
|
|
||||||
{ Name = if not string.match(key, "^%a") then "zzzz" .. key else key },
|
|
||||||
size = UDim2.new(1, 0, 0, 32),
|
|
||||||
automaticsize = Enum.AutomaticSize.Y,
|
|
||||||
|
|
||||||
text = "",
|
|
||||||
|
|
||||||
corner = false,
|
|
||||||
|
|
||||||
activated = function()
|
|
||||||
edit_component(key)
|
|
||||||
end,
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
HorizontalFlex = Enum.UIFlexAlignment.SpaceEvenly,
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
|
||||||
Padding = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.padding {
|
|
||||||
y = UDim.new(0, 4)
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.typography {
|
|
||||||
size = UDim2.new(0, 100, 0, 18),
|
|
||||||
automaticsize = Enum.AutomaticSize.Y,
|
|
||||||
text = key,
|
|
||||||
code = true,
|
|
||||||
wrapped = true,
|
|
||||||
truncate = Enum.TextTruncate.SplitWord,
|
|
||||||
xalignment = Enum.TextXAlignment.Left,
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.typography {
|
|
||||||
size = UDim2.fromOffset(0, 18),
|
|
||||||
automaticsize = Enum.AutomaticSize.Y,
|
|
||||||
|
|
||||||
yalignment = Enum.TextYAlignment.Top,
|
|
||||||
xalignment = Enum.TextXAlignment.Left,
|
|
||||||
text = value,
|
|
||||||
wrapped = true,
|
|
||||||
truncate = Enum.TextTruncate.AtEnd,
|
|
||||||
code = true,
|
|
||||||
|
|
||||||
create "UIFlexItem" {
|
|
||||||
FlexMode = Enum.UIFlexMode.Fill
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
show(function()
|
|
||||||
return props.changes()[key] ~= nil
|
|
||||||
end, function()
|
|
||||||
return ui.typography {
|
|
||||||
text = function()
|
|
||||||
local old = value()
|
|
||||||
local change = props.changes()[key]
|
|
||||||
|
|
||||||
return if old == nil then "(added)"
|
|
||||||
elseif is_removed(change) then "(removed)"
|
|
||||||
else "(changed)"
|
|
||||||
end,
|
|
||||||
disabled = true,
|
|
||||||
textsize = 14,
|
|
||||||
}
|
|
||||||
end),
|
|
||||||
|
|
||||||
create "UISizeConstraint" {
|
|
||||||
MaxSize = Vector2.new(math.huge, 300)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end),
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.typography {text = "Tags"},
|
|
||||||
|
|
||||||
ui.container {
|
|
||||||
Size = UDim2.fromScale(1, 0),
|
|
||||||
AutomaticSize = Enum.AutomaticSize.Y,
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
SortOrder = Enum.SortOrder.Name,
|
|
||||||
},
|
|
||||||
|
|
||||||
indexes(tags, function(value, key)
|
|
||||||
local function did_change()
|
|
||||||
return changes()[key] ~= nil
|
|
||||||
end
|
|
||||||
|
|
||||||
return ui.button {
|
|
||||||
{ Name = if not string.match(key, "^%a") then "zzzz" .. key else key },
|
|
||||||
size = UDim2.new(1, 0, 0, 24),
|
|
||||||
|
|
||||||
text = "",
|
|
||||||
|
|
||||||
corner = false,
|
|
||||||
|
|
||||||
activated = function()
|
|
||||||
if changes()[key] == "tag" then
|
|
||||||
changes()[key] = nil
|
|
||||||
props.components()[key] = nil
|
|
||||||
|
|
||||||
-- notify about the change
|
|
||||||
changes(changes())
|
|
||||||
props.components(props.components())
|
|
||||||
elseif changes()[key] then
|
|
||||||
changes()[key] = nil
|
|
||||||
|
|
||||||
-- notify about the change
|
|
||||||
changes(changes())
|
|
||||||
else
|
|
||||||
changes()[key] = "nil"
|
|
||||||
|
|
||||||
-- notify about the change
|
|
||||||
changes(changes())
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
HorizontalFlex = Enum.UIFlexAlignment.SpaceEvenly,
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
|
||||||
Padding = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.padding {
|
|
||||||
y = UDim.new(0, 4)
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.typography {
|
|
||||||
size = UDim2.fromScale(0, 1),
|
|
||||||
text = key,
|
|
||||||
code = true,
|
|
||||||
wrapped = true,
|
|
||||||
truncate = Enum.TextTruncate.SplitWord,
|
|
||||||
xalignment = Enum.TextXAlignment.Left,
|
|
||||||
|
|
||||||
create "UIFlexItem" {
|
|
||||||
FlexMode = Enum.UIFlexMode.Fill
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
show(did_change, function()
|
|
||||||
return ui.typography {
|
|
||||||
text = function()
|
|
||||||
local old = value()
|
|
||||||
local change = changes()[key]
|
|
||||||
|
|
||||||
return if old == nil then "(added)"
|
|
||||||
elseif is_removed(change) then "(removed)"
|
|
||||||
else "(changed)"
|
|
||||||
end,
|
|
||||||
disabled = true,
|
|
||||||
textsize = 14,
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
}
|
|
||||||
end),
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.row {
|
|
||||||
|
|
||||||
justifycontent = Enum.UIFlexAlignment.Fill,
|
|
||||||
|
|
||||||
ui.button {
|
|
||||||
size = UDim2.fromOffset(100, 30),
|
|
||||||
text = "Delete Id",
|
|
||||||
|
|
||||||
activated = props.delete
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.button {
|
|
||||||
size = UDim2.fromOffset(200, 30),
|
|
||||||
text = "Add Component",
|
|
||||||
|
|
||||||
activated = function()
|
|
||||||
adding(true)
|
|
||||||
end
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
local RunService = game:GetService("RunService")
|
|
||||||
|
|
||||||
local vide = require(script.Parent.Parent.Parent.vide)
|
|
||||||
local loop = require(script.Parent.Parent.Parent.modules.loop)
|
|
||||||
local widget = require(script.widget)
|
|
||||||
|
|
||||||
local cleanup = vide.cleanup
|
|
||||||
|
|
||||||
local home = {
|
|
||||||
class_name = "app" :: "app",
|
|
||||||
name = "Home"
|
|
||||||
}
|
|
||||||
|
|
||||||
function home.mount(_: nil, destroy: () -> ())
|
|
||||||
|
|
||||||
local servers = vide.source {} :: any
|
|
||||||
local app_loop = loop (
|
|
||||||
"app-client-home",
|
|
||||||
servers,
|
|
||||||
|
|
||||||
{i = 1},
|
|
||||||
script.systems.get_core_data
|
|
||||||
)
|
|
||||||
|
|
||||||
cleanup(
|
|
||||||
RunService.Heartbeat:Connect(app_loop)
|
|
||||||
)
|
|
||||||
|
|
||||||
return widget {
|
|
||||||
servers = servers,
|
|
||||||
destroy = destroy
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
return home
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
local Players = game:GetService("Players")
|
|
||||||
|
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local queue = require(script.Parent.Parent.Parent.Parent.Parent.modules.queue)
|
|
||||||
local remotes = require(script.Parent.Parent.Parent.Parent.Parent.modules.remotes)
|
|
||||||
local reverse_connector = require(script.Parent.Parent.Parent.Parent.Parent.modules.reverse_connector)
|
|
||||||
|
|
||||||
return function(data)
|
|
||||||
|
|
||||||
for _, player in Players:GetPlayers() do
|
|
||||||
remotes.ping:fire({
|
|
||||||
host = player,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
remotes.ping:fire({
|
|
||||||
host = "server"
|
|
||||||
})
|
|
||||||
|
|
||||||
local servers_responding = queue(remotes.new_server_registered)
|
|
||||||
local server_update = queue(remotes.update_server_data)
|
|
||||||
local player_removing = queue(Players.PlayerRemoving)
|
|
||||||
|
|
||||||
local n = 0
|
|
||||||
local servers = data :: any
|
|
||||||
local map_to_idx = {
|
|
||||||
server = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return function()
|
|
||||||
|
|
||||||
for connector in servers_responding:iter() do
|
|
||||||
local outgoing = reverse_connector(connector)
|
|
||||||
remotes.bind_to_server_core:fire(outgoing)
|
|
||||||
end
|
|
||||||
|
|
||||||
for player in player_removing:iter() do
|
|
||||||
local indexes = map_to_idx[player]
|
|
||||||
|
|
||||||
if not indexes then continue end
|
|
||||||
|
|
||||||
for _, idx in indexes do
|
|
||||||
servers()[idx] = nil
|
|
||||||
end
|
|
||||||
servers(servers())
|
|
||||||
end
|
|
||||||
|
|
||||||
for connector, packet in server_update:iter() do
|
|
||||||
local outgoing = reverse_connector(connector)
|
|
||||||
|
|
||||||
map_to_idx[outgoing.host] = map_to_idx[outgoing.host] or {}
|
|
||||||
local idx = map_to_idx[outgoing.host][outgoing.to_vm]
|
|
||||||
|
|
||||||
if not idx then
|
|
||||||
-- print("new server")
|
|
||||||
idx = n + 1; n += 1
|
|
||||||
map_to_idx[outgoing.host][outgoing.to_vm] = idx
|
|
||||||
servers()[idx] = {
|
|
||||||
host = outgoing.host,
|
|
||||||
vm = outgoing.to_vm,
|
|
||||||
|
|
||||||
schedulers = vide.source {},
|
|
||||||
worlds = vide.source {}
|
|
||||||
}
|
|
||||||
servers(servers())
|
|
||||||
-- print("set worlds")
|
|
||||||
-- print(servers())
|
|
||||||
end
|
|
||||||
|
|
||||||
local server = servers()[idx]
|
|
||||||
local schedulers = server.schedulers()
|
|
||||||
local worlds = server.worlds()
|
|
||||||
|
|
||||||
table.clear(schedulers)
|
|
||||||
table.clear(worlds)
|
|
||||||
|
|
||||||
for index, data in packet.schedulers do
|
|
||||||
local at = schedulers[index]
|
|
||||||
if at and at.name == data.name and at.id == data.id then continue end
|
|
||||||
schedulers[index] = data
|
|
||||||
end
|
|
||||||
|
|
||||||
for index, data in packet.worlds do
|
|
||||||
local at = worlds[index]
|
|
||||||
if at and at.name == data.name and at.id == data.id then continue end
|
|
||||||
worlds[index] = data
|
|
||||||
end
|
|
||||||
|
|
||||||
server.schedulers(schedulers)
|
|
||||||
server.worlds(worlds)
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,183 +0,0 @@
|
||||||
local Players = game:GetService("Players")
|
|
||||||
|
|
||||||
local ui = require(script.Parent.Parent.Parent.Parent.ui)
|
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local spawn_app = require(script.Parent.Parent.Parent.spawn_app)
|
|
||||||
local overview_scheduler = require(script.Parent.Parent.overview_scheduler)
|
|
||||||
local registry = require(script.Parent.Parent.registry)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local derive = vide.derive
|
|
||||||
local source = vide.source
|
|
||||||
local values = vide.values
|
|
||||||
local show = vide.show
|
|
||||||
|
|
||||||
type props = {
|
|
||||||
|
|
||||||
servers: () -> {
|
|
||||||
{
|
|
||||||
host: "server" | Player,
|
|
||||||
vm: number,
|
|
||||||
|
|
||||||
schedulers: () -> {
|
|
||||||
{id: number, name: string}
|
|
||||||
},
|
|
||||||
worlds: () -> {
|
|
||||||
{id: number, name: string}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
destroy: () -> ()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
|
|
||||||
local selected = source(Players.LocalPlayer)
|
|
||||||
|
|
||||||
local hosts = derive(function()
|
|
||||||
local hosts = {}
|
|
||||||
|
|
||||||
for _, server in props.servers() do
|
|
||||||
local host = server.host
|
|
||||||
hosts[host] = hosts[host] or {}
|
|
||||||
table.insert(hosts[host], server)
|
|
||||||
end
|
|
||||||
|
|
||||||
hosts["all"] = props.servers()
|
|
||||||
|
|
||||||
return hosts
|
|
||||||
end)
|
|
||||||
|
|
||||||
local options = derive(function()
|
|
||||||
local options = {}
|
|
||||||
|
|
||||||
for host, servers in hosts() do
|
|
||||||
options[host] = if type(host) == "string" then host else `@{host.Name}`
|
|
||||||
end
|
|
||||||
|
|
||||||
options[Players.LocalPlayer] = "localplayer"
|
|
||||||
options["all"] = "all"
|
|
||||||
|
|
||||||
return options
|
|
||||||
end)
|
|
||||||
|
|
||||||
local function objects()
|
|
||||||
return hosts()[selected()] or {} :: never
|
|
||||||
end
|
|
||||||
|
|
||||||
local function is_empty()
|
|
||||||
local no_objects = next(objects()) == nil
|
|
||||||
if no_objects then return "No objects found. You may not have permissions to use this." end
|
|
||||||
-- for _, object in objects() do
|
|
||||||
-- if #object.worlds() == 0 then return "No worlds found. Did you forget to set updated?" end
|
|
||||||
-- if #object.schedulers() == 0 then return "No schedulers found. Did you forget to set updated?" end
|
|
||||||
-- end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
return ui.widget {
|
|
||||||
title = "Home",
|
|
||||||
min_size = Vector2.new(230, 200),
|
|
||||||
bind_to_close = props.destroy,
|
|
||||||
|
|
||||||
ui.container {
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
Padding = UDim.new(0, 2),
|
|
||||||
VerticalFlex = Enum.UIFlexAlignment.SpaceEvenly,
|
|
||||||
HorizontalFlex = Enum.UIFlexAlignment.Fill
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.select {
|
|
||||||
size = UDim2.new(1, 0, 0, 32),
|
|
||||||
options = options :: any,
|
|
||||||
selected = selected :: any,
|
|
||||||
update_selected = selected
|
|
||||||
},
|
|
||||||
|
|
||||||
create "ScrollingFrame" {
|
|
||||||
-- Size = UDim2.fromScale(1, 1),
|
|
||||||
CanvasSize = UDim2.new(),
|
|
||||||
AutomaticCanvasSize = Enum.AutomaticSize.Y,
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
ScrollBarThickness = 6,
|
|
||||||
HorizontalScrollBarInset = Enum.ScrollBarInset.Always,
|
|
||||||
|
|
||||||
create "UIFlexItem" {
|
|
||||||
FlexMode = Enum.UIFlexMode.Fill
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.padding {
|
|
||||||
x = UDim.new(0, 1),
|
|
||||||
right = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
HorizontalFlex = Enum.UIFlexAlignment.Fill,
|
|
||||||
Padding = UDim.new(0, 8),
|
|
||||||
Wraps = true
|
|
||||||
},
|
|
||||||
|
|
||||||
show(is_empty, function()
|
|
||||||
return ui.typography {
|
|
||||||
text = is_empty(),
|
|
||||||
xalignment = Enum.TextXAlignment.Left,
|
|
||||||
wrapped = true
|
|
||||||
}
|
|
||||||
end),
|
|
||||||
|
|
||||||
values(objects, function(value, key)
|
|
||||||
|
|
||||||
return ui.pane{
|
|
||||||
name = "",
|
|
||||||
size = UDim2.fromOffset(200, 0),
|
|
||||||
automaticsize = Enum.AutomaticSize.Y,
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
Padding = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.typography {
|
|
||||||
text = `host: {value.host}\tvm id: {value.vm}`,
|
|
||||||
wrapped = true
|
|
||||||
},
|
|
||||||
|
|
||||||
values(value.worlds, function(world)
|
|
||||||
return ui.button {
|
|
||||||
size = UDim2.new(1, 0, 0, 30),
|
|
||||||
text = `World: {world.name}`,
|
|
||||||
|
|
||||||
activated = function()
|
|
||||||
spawn_app.spawn_app(registry, {
|
|
||||||
host = value.host,
|
|
||||||
vm = value.vm,
|
|
||||||
id = world.id
|
|
||||||
})
|
|
||||||
end
|
|
||||||
}
|
|
||||||
end),
|
|
||||||
|
|
||||||
values(value.schedulers, function(scheduler)
|
|
||||||
return ui.button {
|
|
||||||
size = UDim2.new(1, 0, 0, 30),
|
|
||||||
text = `Scheduler: {scheduler.name}`,
|
|
||||||
|
|
||||||
activated = function()
|
|
||||||
spawn_app.spawn_app(overview_scheduler, {
|
|
||||||
host = value.host,
|
|
||||||
vm = value.vm,
|
|
||||||
id = scheduler.id
|
|
||||||
})
|
|
||||||
end
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
local RunService = game:GetService("RunService")
|
|
||||||
|
|
||||||
local vide = require(script.Parent.Parent.Parent.vide)
|
|
||||||
local loop = require(script.Parent.Parent.Parent.modules.loop)
|
|
||||||
local remotes = require(script.Parent.Parent.Parent.modules.remotes)
|
|
||||||
local widget = require(script.widget)
|
|
||||||
|
|
||||||
local source = vide.source
|
|
||||||
local cleanup = vide.cleanup
|
|
||||||
|
|
||||||
local overview_scheduler = {
|
|
||||||
class_name = "app" :: "app",
|
|
||||||
name = "Scheduler"
|
|
||||||
}
|
|
||||||
|
|
||||||
type props = {
|
|
||||||
host: Player | "server",
|
|
||||||
vm: number,
|
|
||||||
id: number
|
|
||||||
}
|
|
||||||
|
|
||||||
function overview_scheduler.mount(props: props, destroy: () -> ())
|
|
||||||
|
|
||||||
local system_data = source {}
|
|
||||||
local system_frames = source {}
|
|
||||||
local system_ids = source {}
|
|
||||||
|
|
||||||
local app_loop = loop (
|
|
||||||
"app-client-scheduler",
|
|
||||||
{
|
|
||||||
host = props.host,
|
|
||||||
vm = props.vm,
|
|
||||||
id = props.id,
|
|
||||||
|
|
||||||
system_ids = system_ids,
|
|
||||||
system_data = system_data,
|
|
||||||
system_frames = system_frames,
|
|
||||||
},
|
|
||||||
|
|
||||||
{i = 1},
|
|
||||||
script.systems.get_scheduler_data
|
|
||||||
)
|
|
||||||
|
|
||||||
cleanup(
|
|
||||||
RunService.Heartbeat:Connect(app_loop)
|
|
||||||
)
|
|
||||||
|
|
||||||
return widget {
|
|
||||||
host = props.host,
|
|
||||||
vm = props.vm,
|
|
||||||
id = props.id,
|
|
||||||
|
|
||||||
system_ids = system_ids,
|
|
||||||
system_data = system_data,
|
|
||||||
system_frames = system_frames,
|
|
||||||
pause_system = function(system: number)
|
|
||||||
remotes.scheduler_system_pause:fire({
|
|
||||||
host = props.host,
|
|
||||||
to_vm = props.vm
|
|
||||||
}, props.id, system, not system_data()[system].paused)
|
|
||||||
end,
|
|
||||||
|
|
||||||
destroy = destroy
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
return overview_scheduler
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local indexes = vide.indexes
|
|
||||||
local derive = vide.derive
|
|
||||||
|
|
||||||
type stack_bar = {
|
|
||||||
values: () -> {{value: number, color: Color3}},
|
|
||||||
selected: (number) -> ()
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: stack_bar)
|
|
||||||
|
|
||||||
local total = derive(function()
|
|
||||||
local total = 0
|
|
||||||
for _, value in props.values() do
|
|
||||||
total += value.value
|
|
||||||
end
|
|
||||||
return total
|
|
||||||
end)
|
|
||||||
|
|
||||||
return create "Frame" {
|
|
||||||
Name = "Graph",
|
|
||||||
Size = UDim2.new(1, 0, 0, 32),
|
|
||||||
|
|
||||||
indexes(props.values, function(value, index)
|
|
||||||
return create "Frame" {
|
|
||||||
|
|
||||||
Size = function()
|
|
||||||
return UDim2.fromScale(value().value / total(), 1)
|
|
||||||
end,
|
|
||||||
|
|
||||||
BackgroundColor3 = function() return value().color end,
|
|
||||||
|
|
||||||
MouseEnter = function()
|
|
||||||
props.selected(index)
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
end),
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local queue = require(script.Parent.Parent.Parent.Parent.Parent.modules.queue)
|
|
||||||
local remotes = require(script.Parent.Parent.Parent.Parent.Parent.modules.remotes)
|
|
||||||
local types = require(script.Parent.Parent.Parent.Parent.Parent.modules.types)
|
|
||||||
|
|
||||||
local batch = vide.batch
|
|
||||||
local cleanup = vide.cleanup
|
|
||||||
|
|
||||||
type SystemId = types.SystemId
|
|
||||||
type SystemData = types.SystemData
|
|
||||||
type SystemFrame = types.SystemFrame
|
|
||||||
|
|
||||||
type context = {
|
|
||||||
host: Player | "server",
|
|
||||||
vm: number,
|
|
||||||
id: number,
|
|
||||||
|
|
||||||
system_ids: vide.Source<{[SystemId]: true}>,
|
|
||||||
system_data: vide.Source<{[SystemId]: types.SystemData}>,
|
|
||||||
system_frames: vide.Source<{[SystemId]: {types.SystemFrame}}>,
|
|
||||||
}
|
|
||||||
|
|
||||||
local MAX_BUFFER_SIZE = 50
|
|
||||||
|
|
||||||
return function(context: context)
|
|
||||||
local outgoing = {
|
|
||||||
host = context.host,
|
|
||||||
to_vm = context.vm
|
|
||||||
}
|
|
||||||
|
|
||||||
remotes.request_scheduler:fire(outgoing, context.id)
|
|
||||||
|
|
||||||
local scheduler_static_data_updated = queue(remotes.scheduler_system_static_update)
|
|
||||||
local scheduler_frame_data_updated = queue(remotes.scheduler_system_update)
|
|
||||||
|
|
||||||
cleanup(function()
|
|
||||||
remotes.disconnect_scheduler:fire(outgoing, context.id)
|
|
||||||
end)
|
|
||||||
|
|
||||||
return function()
|
|
||||||
|
|
||||||
batch(function()
|
|
||||||
for incoming, scheduler, id, new_data in scheduler_static_data_updated:iter() do
|
|
||||||
if incoming.host ~= context.host then continue end
|
|
||||||
if incoming.from_vm ~= context.vm then continue end
|
|
||||||
if scheduler ~= context.id then continue end
|
|
||||||
|
|
||||||
if new_data == nil then
|
|
||||||
context.system_ids()[id] = nil
|
|
||||||
context.system_data()[id] = nil
|
|
||||||
context.system_frames()[id] = nil
|
|
||||||
|
|
||||||
context.system_ids(context.system_ids())
|
|
||||||
context.system_data(context.system_data())
|
|
||||||
context.system_frames(context.system_frames())
|
|
||||||
else
|
|
||||||
context.system_ids()[id] = true
|
|
||||||
context.system_data()[id] = new_data
|
|
||||||
context.system_frames()[id] = context.system_frames()[id] or {}
|
|
||||||
|
|
||||||
context.system_ids(context.system_ids())
|
|
||||||
context.system_data(context.system_data())
|
|
||||||
context.system_frames(context.system_frames())
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
for incoming, scheduler, id, f, s in scheduler_frame_data_updated:iter() do
|
|
||||||
if incoming.host ~= context.host then continue end
|
|
||||||
if incoming.from_vm ~= context.vm then continue end
|
|
||||||
if scheduler ~= context.id then continue end
|
|
||||||
if context.system_frames()[id] == nil then continue end
|
|
||||||
|
|
||||||
-- look where to append the frame to
|
|
||||||
local frames = context.system_frames()[id]
|
|
||||||
local f_data = {i = f, s = s}
|
|
||||||
local added = false
|
|
||||||
|
|
||||||
--- since it's unreliable we have to constantly check if we arent out of order
|
|
||||||
for i, frame in frames do
|
|
||||||
if frame.i == f then frames[i] = f_data; continue end
|
|
||||||
if frame.i > f then continue end
|
|
||||||
table.insert(frames, i, f_data)
|
|
||||||
table.remove(frames, MAX_BUFFER_SIZE + 1)
|
|
||||||
added = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
if #frames <= MAX_BUFFER_SIZE and added == false then
|
|
||||||
table.insert(frames, f_data)
|
|
||||||
end
|
|
||||||
|
|
||||||
context.system_frames(context.system_frames())
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,412 +0,0 @@
|
||||||
local ui = require(script.Parent.Parent.Parent.Parent.ui)
|
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local convert_units = require(script.Parent.Parent.Parent.Parent.modules.convert_units)
|
|
||||||
local types = require(script.Parent.Parent.Parent.Parent.modules.types)
|
|
||||||
local spawn_app = require(script.Parent.Parent.Parent.spawn_app)
|
|
||||||
local system_widget = require(script.Parent.Parent.system)
|
|
||||||
local stack_bar = require(script.Parent.stack_bar)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local indexes = vide.indexes
|
|
||||||
local values = vide.values
|
|
||||||
local changed = vide.changed
|
|
||||||
local source = vide.source
|
|
||||||
local derive = vide.derive
|
|
||||||
|
|
||||||
type SystemId = number
|
|
||||||
|
|
||||||
type props = {
|
|
||||||
host: Player | "server",
|
|
||||||
vm: number,
|
|
||||||
id: number,
|
|
||||||
|
|
||||||
system_ids: () -> {[SystemId]: true},
|
|
||||||
system_data: () -> {[SystemId]: types.SystemData},
|
|
||||||
system_frames: () -> {[SystemId]: {types.SystemFrame}},
|
|
||||||
|
|
||||||
pause_system: (SystemId) -> (),
|
|
||||||
destroy: () -> ()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
local function color(n: number)
|
|
||||||
return Color3.fromHSV((0.15 * (n-1)) % 1, 1, 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
local sort_by_options = {
|
|
||||||
"Name",
|
|
||||||
"Id",
|
|
||||||
"Frame Time"
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
|
|
||||||
local selected = source(0)
|
|
||||||
local systems_query = source("")
|
|
||||||
|
|
||||||
local sort_by = source(2)
|
|
||||||
|
|
||||||
local max_frametime = derive(function()
|
|
||||||
local max = 0
|
|
||||||
for _, frames in props.system_frames() do
|
|
||||||
local sum = 0
|
|
||||||
|
|
||||||
for _, frame in frames do
|
|
||||||
sum += frame.s
|
|
||||||
end
|
|
||||||
|
|
||||||
max = math.max(max, sum / #frames)
|
|
||||||
end
|
|
||||||
return max
|
|
||||||
end)
|
|
||||||
|
|
||||||
local map_phases_to_systems = derive(function()
|
|
||||||
local phases: {[false | string]: {number}} = {[false] = {}}
|
|
||||||
for id, data in props.system_data() do
|
|
||||||
if phases[data.phase or false] == nil then phases[data.phase or false] = {} end
|
|
||||||
table.insert(phases[data.phase or false], id)
|
|
||||||
end
|
|
||||||
return phases
|
|
||||||
end)
|
|
||||||
|
|
||||||
local function system(id: number)
|
|
||||||
local gui_state = source(Enum.GuiState.Idle)
|
|
||||||
|
|
||||||
local function frame_time()
|
|
||||||
local sum = 0
|
|
||||||
local frames = props.system_frames()[id]
|
|
||||||
|
|
||||||
for _, frame in frames do
|
|
||||||
sum += frame.s
|
|
||||||
end
|
|
||||||
|
|
||||||
return sum / #frames
|
|
||||||
end
|
|
||||||
|
|
||||||
local b = create "ImageButton" {
|
|
||||||
Name = function()
|
|
||||||
return props.system_data()[id].name
|
|
||||||
end,
|
|
||||||
Size = UDim2.new(1, 0, 0, 32),
|
|
||||||
|
|
||||||
LayoutOrder = function()
|
|
||||||
return if sort_by() == 3 then 1e9 - frame_time() * 1e8 else id
|
|
||||||
end,
|
|
||||||
|
|
||||||
BackgroundColor3 = function()
|
|
||||||
return if gui_state() == Enum.GuiState.Press then
|
|
||||||
ui.theme.bg[-1]()
|
|
||||||
elseif gui_state() == Enum.GuiState.Hover then
|
|
||||||
ui.theme.bg[6]()
|
|
||||||
else
|
|
||||||
ui.theme.bg[3]()
|
|
||||||
end,
|
|
||||||
|
|
||||||
Visible = function()
|
|
||||||
return not not string.match(props.system_data()[id].name, systems_query())
|
|
||||||
end,
|
|
||||||
|
|
||||||
changed("GuiState", gui_state),
|
|
||||||
|
|
||||||
Activated = function()
|
|
||||||
spawn_app.spawn_app(system_widget, {
|
|
||||||
host = props.host,
|
|
||||||
vm = props.vm,
|
|
||||||
|
|
||||||
scheduler = props.id,
|
|
||||||
system = id,
|
|
||||||
name = props.system_data()[id].name
|
|
||||||
})
|
|
||||||
end,
|
|
||||||
|
|
||||||
MouseButton2Click = function()
|
|
||||||
props.pause_system(id)
|
|
||||||
end,
|
|
||||||
|
|
||||||
-- create a frame that ignores all rules!
|
|
||||||
create "Folder" {
|
|
||||||
create "Frame" {
|
|
||||||
Position = UDim2.new(0, 0, 1, 4),
|
|
||||||
AnchorPoint = Vector2.new(0, 1),
|
|
||||||
Size = function()
|
|
||||||
return UDim2.new(frame_time() / max_frametime(), 0, 0, 1)
|
|
||||||
end,
|
|
||||||
|
|
||||||
BackgroundColor3 = ui.theme.fg_on_bg_high[0]
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UIStroke" {
|
|
||||||
Color = ui.theme.bg[-3]
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UICorner" {
|
|
||||||
CornerRadius = UDim.new(0, 4)
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
|
||||||
HorizontalFlex = Enum.UIFlexAlignment.SpaceEvenly,
|
|
||||||
Padding = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.padding {
|
|
||||||
x = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
Size = UDim2.fromOffset(16, 16),
|
|
||||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
||||||
|
|
||||||
BackgroundColor3 = color(id),
|
|
||||||
|
|
||||||
create "UICorner" {
|
|
||||||
CornerRadius = UDim.new(1, 0)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.typography {
|
|
||||||
automaticsize = Enum.AutomaticSize.None,
|
|
||||||
text = function()
|
|
||||||
return props.system_data()[id].name
|
|
||||||
end,
|
|
||||||
|
|
||||||
truncate = Enum.TextTruncate.SplitWord,
|
|
||||||
xalignment = Enum.TextXAlignment.Left,
|
|
||||||
disabled = function()
|
|
||||||
return props.system_data()[id].paused
|
|
||||||
end,
|
|
||||||
|
|
||||||
create "UIFlexItem" {
|
|
||||||
FlexMode = Enum.UIFlexMode.Fill,
|
|
||||||
GrowRatio = 1,
|
|
||||||
ShrinkRatio = 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.typography {
|
|
||||||
automaticsize = Enum.AutomaticSize.XY,
|
|
||||||
text = function()
|
|
||||||
local sum = 0
|
|
||||||
local frames = props.system_frames()[id]
|
|
||||||
|
|
||||||
for _, frame in frames do
|
|
||||||
sum += frame.s
|
|
||||||
end
|
|
||||||
|
|
||||||
return `{convert_units("s", sum / #frames)}`
|
|
||||||
end,
|
|
||||||
|
|
||||||
xalignment = Enum.TextXAlignment.Right,
|
|
||||||
|
|
||||||
disabled = true,
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
end
|
|
||||||
|
|
||||||
return ui.widget {
|
|
||||||
title = "Scheduler",
|
|
||||||
subtitle = `host: {props.host} vm: {props.vm} id: {props.id}`,
|
|
||||||
|
|
||||||
min_size = Vector2.new(200, 300),
|
|
||||||
|
|
||||||
bind_to_close = props.destroy,
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
Name = "Elements",
|
|
||||||
Size = UDim2.fromScale(1, 1),
|
|
||||||
AutomaticSize = Enum.AutomaticSize.Y,
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Bottom,
|
|
||||||
FillDirection = Enum.FillDirection.Vertical,
|
|
||||||
VerticalFlex = Enum.UIFlexAlignment.SpaceBetween,
|
|
||||||
Padding = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
Size = UDim2.fromScale(1, 0),
|
|
||||||
AutomaticSize = Enum.AutomaticSize.Y,
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
-- create "UIFlexItem" {
|
|
||||||
-- FlexMode = Enum.UIFlexMode.Custom,
|
|
||||||
-- GrowRatio = 0,
|
|
||||||
-- ShrinkRatio = 0
|
|
||||||
-- },
|
|
||||||
|
|
||||||
ui.pane {
|
|
||||||
name = "Overview",
|
|
||||||
size = UDim2.fromScale(1, 0),
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
FillDirection = Enum.FillDirection.Vertical
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.typography {
|
|
||||||
text = function()
|
|
||||||
local run_time = 0
|
|
||||||
for id, frames in props.system_frames() do
|
|
||||||
if props.system_data()[id].paused then continue end
|
|
||||||
local sum = 0
|
|
||||||
|
|
||||||
for _, frame in frames do
|
|
||||||
sum += frame.s
|
|
||||||
end
|
|
||||||
|
|
||||||
run_time += sum / #frames
|
|
||||||
end
|
|
||||||
|
|
||||||
return `Run time: {convert_units("s", run_time)}`
|
|
||||||
end
|
|
||||||
},
|
|
||||||
|
|
||||||
stack_bar {
|
|
||||||
values = function()
|
|
||||||
local v = {}
|
|
||||||
|
|
||||||
local system_ids = props.system_ids()
|
|
||||||
local system_frames = props.system_frames()
|
|
||||||
|
|
||||||
for i = 1, table.maxn(system_ids) do
|
|
||||||
if system_ids[i] == nil then continue end
|
|
||||||
if props.system_data()[i].paused then continue end
|
|
||||||
|
|
||||||
local sum = 0
|
|
||||||
local frames = system_frames[i]
|
|
||||||
|
|
||||||
for _, frame in frames do
|
|
||||||
sum += frame.s
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(v, {value = sum / #frames, color = color(i)})
|
|
||||||
end
|
|
||||||
|
|
||||||
return v
|
|
||||||
end,
|
|
||||||
selected = selected
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.row {
|
|
||||||
justifycontent = Enum.UIFlexAlignment.Fill,
|
|
||||||
ui.button {
|
|
||||||
text = "Pause all",
|
|
||||||
|
|
||||||
activated = function()
|
|
||||||
for system, data in props.system_data() do
|
|
||||||
if data.paused then continue end
|
|
||||||
props.pause_system(system)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.button {
|
|
||||||
text = "Resume all",
|
|
||||||
|
|
||||||
activated = function()
|
|
||||||
for system, data in props.system_data() do
|
|
||||||
if not data.paused then continue end
|
|
||||||
props.pause_system(system)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.select {
|
|
||||||
size = UDim2.new(1, 0, 0, 30),
|
|
||||||
|
|
||||||
options = sort_by_options,
|
|
||||||
selected = sort_by,
|
|
||||||
update_selected = function(new)
|
|
||||||
-- print(new)
|
|
||||||
sort_by(new)
|
|
||||||
end
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.textfield {
|
|
||||||
size = UDim2.new(1, 0, 0, 36),
|
|
||||||
|
|
||||||
placeholder = "System Match",
|
|
||||||
|
|
||||||
oninput = systems_query,
|
|
||||||
},
|
|
||||||
|
|
||||||
create "ScrollingFrame" {
|
|
||||||
Name = "Systems",
|
|
||||||
|
|
||||||
Size = UDim2.fromScale(1, 0),
|
|
||||||
CanvasSize = UDim2.new(),
|
|
||||||
AutomaticCanvasSize = Enum.AutomaticSize.Y,
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
ScrollBarThickness = 6,
|
|
||||||
VerticalScrollBarInset = Enum.ScrollBarInset.Always,
|
|
||||||
ScrollBarImageColor3 = ui.theme.fg_on_bg_low[3],
|
|
||||||
|
|
||||||
create "UIFlexItem" {
|
|
||||||
FlexMode = Enum.UIFlexMode.Fill
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
FillDirection = Enum.FillDirection.Vertical,
|
|
||||||
Padding = UDim.new(0, 8),
|
|
||||||
|
|
||||||
SortOrder = function()
|
|
||||||
return if sort_by() == 1 then Enum.SortOrder.Name else Enum.SortOrder.LayoutOrder
|
|
||||||
end
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.padding {
|
|
||||||
y = UDim.new(0, 1),
|
|
||||||
x = UDim.new(0, 1)
|
|
||||||
},
|
|
||||||
|
|
||||||
values(function()
|
|
||||||
return map_phases_to_systems()[false]
|
|
||||||
end, system),
|
|
||||||
|
|
||||||
indexes(map_phases_to_systems, function(systems, phase)
|
|
||||||
if phase == false then return {} end
|
|
||||||
local expanded = source(true)
|
|
||||||
-- print(systems())
|
|
||||||
return ui.accordion {
|
|
||||||
expanded = expanded,
|
|
||||||
set_expanded = expanded,
|
|
||||||
text = phase,
|
|
||||||
|
|
||||||
ui.container {
|
|
||||||
Size = UDim2.fromScale(1, 0),
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
FillDirection = Enum.FillDirection.Vertical,
|
|
||||||
Padding = UDim.new(0, 8),
|
|
||||||
|
|
||||||
SortOrder = function()
|
|
||||||
return if sort_by() == 1 then Enum.SortOrder.Name else Enum.SortOrder.LayoutOrder
|
|
||||||
end
|
|
||||||
},
|
|
||||||
|
|
||||||
values(systems, system)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end),
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,154 +0,0 @@
|
||||||
local ContextActionService = game:GetService("ContextActionService")
|
|
||||||
local RunService = game:GetService("RunService")
|
|
||||||
|
|
||||||
local vide = require(script.Parent.Parent.Parent.vide)
|
|
||||||
local loop = require(script.Parent.Parent.Parent.modules.loop)
|
|
||||||
local spawn_app = require(script.Parent.Parent.spawn_app)
|
|
||||||
local entity_widget = require(script.Parent.entity)
|
|
||||||
local widget = require(script.widget)
|
|
||||||
|
|
||||||
local source = vide.source
|
|
||||||
local effect = vide.effect
|
|
||||||
local cleanup = vide.cleanup
|
|
||||||
|
|
||||||
local overview_query = {
|
|
||||||
class_name = "app" :: "app",
|
|
||||||
name = "Query"
|
|
||||||
}
|
|
||||||
|
|
||||||
type props = {
|
|
||||||
host: Player | "server",
|
|
||||||
vm: number,
|
|
||||||
id: number
|
|
||||||
}
|
|
||||||
|
|
||||||
function overview_query.mount(props: props, destroy: () -> ())
|
|
||||||
|
|
||||||
-- the entity id
|
|
||||||
local current_entity = source(nil)
|
|
||||||
-- enables picking
|
|
||||||
local enable_pick = source(false)
|
|
||||||
-- the entity data as a string
|
|
||||||
local entity_hovering_over = source()
|
|
||||||
-- the part the player is hovering over
|
|
||||||
local hovering_over = source()
|
|
||||||
|
|
||||||
local validate_query = source("")
|
|
||||||
local ok = source(false)
|
|
||||||
local msg = source("")
|
|
||||||
|
|
||||||
local query = source("")
|
|
||||||
local primary_entity = source()
|
|
||||||
local columns = source({})
|
|
||||||
local from = source(1)
|
|
||||||
local upto = source(25)
|
|
||||||
local total_entities = source(0)
|
|
||||||
|
|
||||||
local paused = source(false)
|
|
||||||
local refresh = source(false)
|
|
||||||
|
|
||||||
-- check if the query and columns are properly
|
|
||||||
local app_loop = loop (
|
|
||||||
"app-client-registry",
|
|
||||||
{
|
|
||||||
host = props.host,
|
|
||||||
vm = props.vm,
|
|
||||||
id = props.id,
|
|
||||||
|
|
||||||
enable_pick = enable_pick,
|
|
||||||
entity_hovering_over = entity_hovering_over,
|
|
||||||
hovering_over = hovering_over,
|
|
||||||
set_entity = current_entity,
|
|
||||||
|
|
||||||
columns = columns,
|
|
||||||
query = query,
|
|
||||||
primary_entity = primary_entity,
|
|
||||||
paused = paused,
|
|
||||||
refresh = refresh,
|
|
||||||
|
|
||||||
total_entities = total_entities,
|
|
||||||
|
|
||||||
from = from,
|
|
||||||
upto = upto,
|
|
||||||
|
|
||||||
validate_query = validate_query,
|
|
||||||
ok = ok,
|
|
||||||
msg = msg
|
|
||||||
},
|
|
||||||
|
|
||||||
{i = 1},
|
|
||||||
script.systems.validate_query,
|
|
||||||
script.systems.obtain_query_data,
|
|
||||||
script.systems.send_workspace_entity,
|
|
||||||
script.systems.highlight_workspace_entity
|
|
||||||
)
|
|
||||||
|
|
||||||
cleanup(RunService.Heartbeat:Connect(app_loop))
|
|
||||||
|
|
||||||
local function open_entity_widget(_, state: Enum.UserInputState)
|
|
||||||
local entity = current_entity()
|
|
||||||
|
|
||||||
if state ~= Enum.UserInputState.Begin then return end
|
|
||||||
if entity == nil then return end
|
|
||||||
|
|
||||||
enable_pick(false)
|
|
||||||
entity_hovering_over(nil)
|
|
||||||
hovering_over(nil)
|
|
||||||
|
|
||||||
spawn_app.spawn_app(entity_widget, {
|
|
||||||
host = props.host,
|
|
||||||
vm = props.vm,
|
|
||||||
id = props.id,
|
|
||||||
entity = entity
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
effect(function()
|
|
||||||
local picking = enable_pick()
|
|
||||||
local key = `select entity:{props.host} {props.vm} {props.id}`
|
|
||||||
|
|
||||||
if picking then
|
|
||||||
ContextActionService:BindAction(key, open_entity_widget, false, Enum.UserInputType.MouseButton1)
|
|
||||||
end
|
|
||||||
|
|
||||||
cleanup(function()
|
|
||||||
ContextActionService:UnbindAction(key)
|
|
||||||
end)
|
|
||||||
|
|
||||||
end)
|
|
||||||
|
|
||||||
return widget {
|
|
||||||
host = props.host,
|
|
||||||
vm = props.vm,
|
|
||||||
id = props.id,
|
|
||||||
|
|
||||||
validate_query = validate_query,
|
|
||||||
|
|
||||||
update_system_query = query,
|
|
||||||
current_query = query,
|
|
||||||
total_rows_per_page = source(25),
|
|
||||||
set_rows_per_page = source(25),
|
|
||||||
|
|
||||||
primary_entity = primary_entity,
|
|
||||||
|
|
||||||
from = from,
|
|
||||||
upto = upto,
|
|
||||||
total_entities = total_entities,
|
|
||||||
paused = paused,
|
|
||||||
refresh = refresh,
|
|
||||||
|
|
||||||
enable_pick = enable_pick,
|
|
||||||
entity_hovering_over = entity_hovering_over,
|
|
||||||
hovering_over = hovering_over,
|
|
||||||
|
|
||||||
ok = ok,
|
|
||||||
msg = msg,
|
|
||||||
|
|
||||||
columns = columns,
|
|
||||||
|
|
||||||
destroy = destroy
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
return overview_query
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
local queue = require(script.Parent.Parent.Parent.Parent.Parent.modules.queue)
|
|
||||||
local remotes = require(script.Parent.Parent.Parent.Parent.Parent.modules.remotes)
|
|
||||||
|
|
||||||
type Context = {
|
|
||||||
host: Player | "server",
|
|
||||||
vm: number,
|
|
||||||
id: number,
|
|
||||||
|
|
||||||
enable_pick: () -> boolean,
|
|
||||||
entity_hovering_over: (string) -> (),
|
|
||||||
set_entity: (number) -> (),
|
|
||||||
hovering_over: (BasePart) -> ()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(context: Context)
|
|
||||||
|
|
||||||
local send_mouse_entity = queue(remotes.send_mouse_entity)
|
|
||||||
|
|
||||||
return function()
|
|
||||||
|
|
||||||
for incoming, id, to_highlight, entity, components in send_mouse_entity:iter() do
|
|
||||||
if incoming.host ~= context.host then continue end
|
|
||||||
if incoming.from_vm ~= context.vm then continue end
|
|
||||||
if id ~= context.id then continue end
|
|
||||||
if context.enable_pick() == false then continue end
|
|
||||||
context.hovering_over(to_highlight)
|
|
||||||
context.entity_hovering_over(components)
|
|
||||||
context.set_entity(entity)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local queue = require(script.Parent.Parent.Parent.Parent.Parent.modules.queue)
|
|
||||||
local remotes = require(script.Parent.Parent.Parent.Parent.Parent.modules.remotes)
|
|
||||||
|
|
||||||
local effect = vide.effect
|
|
||||||
local batch = vide.batch
|
|
||||||
local cleanup = vide.cleanup
|
|
||||||
|
|
||||||
type Context = {
|
|
||||||
host: Player | "server",
|
|
||||||
vm: number,
|
|
||||||
id: number,
|
|
||||||
|
|
||||||
columns: vide.Source<{ { any } }>,
|
|
||||||
query: () -> string,
|
|
||||||
|
|
||||||
paused: () -> boolean,
|
|
||||||
refresh: vide.Source<boolean>,
|
|
||||||
|
|
||||||
total_entities: (number) -> (),
|
|
||||||
|
|
||||||
from: () -> number,
|
|
||||||
upto: () -> number,
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
local function generate_random_query_id()
|
|
||||||
return math.random(2 ^ 31 - 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
return function(context: Context)
|
|
||||||
|
|
||||||
local query_changed = false
|
|
||||||
local page_changed = false
|
|
||||||
|
|
||||||
effect(function()
|
|
||||||
if #context.query() > 0 then
|
|
||||||
query_changed = true
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
effect(function()
|
|
||||||
context.from()
|
|
||||||
context.upto()
|
|
||||||
page_changed = true
|
|
||||||
end)
|
|
||||||
|
|
||||||
local current_query_id = -1
|
|
||||||
local query_last_frame = 0
|
|
||||||
local update_query_result = queue(remotes.update_query_result)
|
|
||||||
local count_updated = queue(remotes.count_total_entities)
|
|
||||||
|
|
||||||
local columns = context.columns
|
|
||||||
local outgoing = {
|
|
||||||
host = context.host,
|
|
||||||
to_vm = context.vm
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup(function()
|
|
||||||
remotes.disconnect_query:fire(outgoing, current_query_id)
|
|
||||||
end)
|
|
||||||
|
|
||||||
local should_refresh = false
|
|
||||||
effect(function()
|
|
||||||
if context.refresh() ~= true then return end
|
|
||||||
context.refresh(false)
|
|
||||||
should_refresh = true
|
|
||||||
end)
|
|
||||||
|
|
||||||
local paused_state = context.paused()
|
|
||||||
local paused_updated = false
|
|
||||||
effect(function()
|
|
||||||
paused_updated = true
|
|
||||||
paused_state = context.paused()
|
|
||||||
end)
|
|
||||||
|
|
||||||
return function()
|
|
||||||
|
|
||||||
if query_changed then
|
|
||||||
columns({})
|
|
||||||
remotes.disconnect_query:fire(outgoing, current_query_id)
|
|
||||||
current_query_id = generate_random_query_id()
|
|
||||||
-- print("requesting new query", current_query_id)
|
|
||||||
remotes.request_query:fire(outgoing, context.id, current_query_id, context.query())
|
|
||||||
remotes.advance_query_page:fire(outgoing, current_query_id, context.from(), context.upto())
|
|
||||||
query_last_frame = 0
|
|
||||||
query_changed = false
|
|
||||||
|
|
||||||
remotes.pause_query:fire(outgoing, current_query_id, paused_state)
|
|
||||||
end
|
|
||||||
|
|
||||||
if page_changed then
|
|
||||||
remotes.advance_query_page:fire(outgoing, current_query_id, context.from(), context.upto())
|
|
||||||
page_changed = false
|
|
||||||
end
|
|
||||||
|
|
||||||
for incoming, query, value in count_updated:iter() do
|
|
||||||
if query ~= current_query_id then continue end
|
|
||||||
context.total_entities(value)
|
|
||||||
end
|
|
||||||
|
|
||||||
if paused_updated then
|
|
||||||
remotes.pause_query:fire(outgoing, current_query_id, paused_state)
|
|
||||||
paused_updated = false
|
|
||||||
end
|
|
||||||
|
|
||||||
if should_refresh then
|
|
||||||
remotes.refresh_results:fire(outgoing, current_query_id)
|
|
||||||
should_refresh = false
|
|
||||||
end
|
|
||||||
|
|
||||||
batch(function()
|
|
||||||
for incoming, query, frame, column, row, value in update_query_result:iter() do
|
|
||||||
if query ~= current_query_id then continue end
|
|
||||||
if frame < query_last_frame - 10 then continue end
|
|
||||||
query_last_frame = math.max(query_last_frame, frame)
|
|
||||||
|
|
||||||
if columns()[column] == nil then
|
|
||||||
columns()[column] = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
columns()[column][row] = value
|
|
||||||
columns(columns())
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
local UserInputService = game:GetService("UserInputService")
|
|
||||||
|
|
||||||
local remotes = require(script.Parent.Parent.Parent.Parent.Parent.modules.remotes)
|
|
||||||
|
|
||||||
type Context = {
|
|
||||||
host: Player | "server",
|
|
||||||
vm: number,
|
|
||||||
id: number,
|
|
||||||
|
|
||||||
enable_pick: () -> boolean,
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(context: Context)
|
|
||||||
|
|
||||||
if context.enable_pick() == false then return end
|
|
||||||
|
|
||||||
local location = UserInputService:GetMouseLocation()
|
|
||||||
local camera = workspace.CurrentCamera
|
|
||||||
|
|
||||||
local ray = camera:ViewportPointToRay(location.X, location.Y)
|
|
||||||
|
|
||||||
remotes.send_mouse_pointer:fire(
|
|
||||||
{
|
|
||||||
host = context.host,
|
|
||||||
to_vm = context.vm
|
|
||||||
},
|
|
||||||
context.id,
|
|
||||||
ray.Origin,
|
|
||||||
ray.Direction
|
|
||||||
)
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
local Players = game:GetService("Players")
|
|
||||||
|
|
||||||
local jecs = require(script.Parent.Parent.Parent.Parent.Parent.jecs)
|
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local queue = require(script.Parent.Parent.Parent.Parent.Parent.modules.queue)
|
|
||||||
local remotes = require(script.Parent.Parent.Parent.Parent.Parent.modules.remotes)
|
|
||||||
|
|
||||||
local effect = vide.effect
|
|
||||||
|
|
||||||
type Context = {
|
|
||||||
host: Player | "server",
|
|
||||||
vm: number,
|
|
||||||
id: number,
|
|
||||||
|
|
||||||
validate_query: () -> string,
|
|
||||||
|
|
||||||
msg: (string) -> (),
|
|
||||||
ok: (boolean) -> (),
|
|
||||||
primary_entity: (any?) -> ()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(context: Context)
|
|
||||||
|
|
||||||
local query_changed = false
|
|
||||||
|
|
||||||
effect(function()
|
|
||||||
context.validate_query()
|
|
||||||
|
|
||||||
query_changed = true
|
|
||||||
end)
|
|
||||||
|
|
||||||
local n = 0
|
|
||||||
local already_validated = false
|
|
||||||
local MIN_DELAY_UNTIL_VALIDATE = 0
|
|
||||||
|
|
||||||
local validate_result = queue(remotes.validate_result)
|
|
||||||
|
|
||||||
if context.host == Players.LocalPlayer then
|
|
||||||
MIN_DELAY_UNTIL_VALIDATE = 0.3
|
|
||||||
elseif context.host == "server" then
|
|
||||||
MIN_DELAY_UNTIL_VALIDATE = 0.5
|
|
||||||
else
|
|
||||||
MIN_DELAY_UNTIL_VALIDATE = 0.5
|
|
||||||
end
|
|
||||||
|
|
||||||
return function(dt)
|
|
||||||
if query_changed then
|
|
||||||
n = 0
|
|
||||||
already_validated = false
|
|
||||||
query_changed = false
|
|
||||||
|
|
||||||
context.ok(false)
|
|
||||||
context.msg("")
|
|
||||||
end
|
|
||||||
|
|
||||||
for incoming, world, query, terms, ok, msg in validate_result:iter() do
|
|
||||||
if incoming.host ~= context.host then continue end
|
|
||||||
if incoming.from_vm ~= context.vm then continue end
|
|
||||||
if world ~= context.id then continue end
|
|
||||||
if query ~= context.validate_query() then continue end
|
|
||||||
|
|
||||||
context.ok(ok)
|
|
||||||
context.msg(msg or "")
|
|
||||||
context.primary_entity(nil)
|
|
||||||
|
|
||||||
if not terms then continue end
|
|
||||||
if #terms.include + #terms.exclude + #terms.with > 1 then continue end
|
|
||||||
local first = terms.include[1]
|
|
||||||
|
|
||||||
if first == nil then continue end
|
|
||||||
if jecs.IS_PAIR(first) then continue end
|
|
||||||
context.primary_entity(terms.include[1])
|
|
||||||
end
|
|
||||||
|
|
||||||
n += dt
|
|
||||||
if n < MIN_DELAY_UNTIL_VALIDATE then return end
|
|
||||||
if already_validated then return end
|
|
||||||
|
|
||||||
if context.validate_query() == "" then
|
|
||||||
context.ok(false)
|
|
||||||
context.msg("empty query")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
already_validated = true
|
|
||||||
|
|
||||||
remotes.validate_query:fire(
|
|
||||||
{
|
|
||||||
host = context.host,
|
|
||||||
to_vm = context.vm
|
|
||||||
},
|
|
||||||
context.id,
|
|
||||||
context.validate_query()
|
|
||||||
)
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,503 +0,0 @@
|
||||||
local RunService = game:GetService("RunService")
|
|
||||||
local UserInputService = game:GetService("UserInputService")
|
|
||||||
|
|
||||||
local ui = require(script.Parent.Parent.Parent.Parent.ui)
|
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local tooltip = require(script.Parent.Parent.Parent.components.tooltip)
|
|
||||||
local spawn_app = require(script.Parent.Parent.Parent.spawn_app)
|
|
||||||
local entity = require(script.Parent.Parent.entity)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local effect = vide.effect
|
|
||||||
local source = vide.source
|
|
||||||
local show = vide.show
|
|
||||||
|
|
||||||
type SystemId = number
|
|
||||||
|
|
||||||
type props = {
|
|
||||||
host: Player | "server",
|
|
||||||
vm: number,
|
|
||||||
id: number,
|
|
||||||
|
|
||||||
validate_query: (string) -> (),
|
|
||||||
ok: () -> boolean,
|
|
||||||
msg: () -> string,
|
|
||||||
|
|
||||||
paused: vide.Source<boolean>,
|
|
||||||
refresh: (boolean) -> (),
|
|
||||||
|
|
||||||
from: vide.Source<number>,
|
|
||||||
upto: vide.Source<number>,
|
|
||||||
|
|
||||||
primary_entity: () -> number?,
|
|
||||||
|
|
||||||
update_system_query: (query: string) -> (),
|
|
||||||
current_query: () -> string,
|
|
||||||
total_entities: () -> number,
|
|
||||||
|
|
||||||
enable_pick: vide.Source<boolean>,
|
|
||||||
entity_hovering_over: () -> string,
|
|
||||||
hovering_over: () -> BasePart,
|
|
||||||
|
|
||||||
columns: () -> {{any}},
|
|
||||||
|
|
||||||
destroy: () -> ()
|
|
||||||
}
|
|
||||||
|
|
||||||
local mouse_location = source(Vector2.zero)
|
|
||||||
|
|
||||||
RunService.PreRender:Connect(function()
|
|
||||||
mouse_location(UserInputService:GetMouseLocation())
|
|
||||||
end)
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
|
|
||||||
local page_input = source("1")
|
|
||||||
local rows_input = source("25")
|
|
||||||
|
|
||||||
local page = source(1)
|
|
||||||
local rows = source(20)
|
|
||||||
|
|
||||||
effect(function()
|
|
||||||
page_input(tostring(page()))
|
|
||||||
end)
|
|
||||||
|
|
||||||
effect(function()
|
|
||||||
rows_input(tostring(rows()))
|
|
||||||
end)
|
|
||||||
|
|
||||||
effect(function()
|
|
||||||
local page = page()
|
|
||||||
local rows_per_page = rows()
|
|
||||||
|
|
||||||
local from = (page - 1) * rows_per_page + 1
|
|
||||||
local upto = from + rows_per_page - 1
|
|
||||||
|
|
||||||
props.from(from)
|
|
||||||
props.upto(upto)
|
|
||||||
end)
|
|
||||||
|
|
||||||
local row_template = {
|
|
||||||
Size = UDim2.new(0, 0, 0, 26),
|
|
||||||
AutomaticSize = Enum.AutomaticSize.X
|
|
||||||
} :: any
|
|
||||||
|
|
||||||
return ui.widget {
|
|
||||||
title = "Querying",
|
|
||||||
subtitle = `host: {props.host} vm: {props.vm} id: {props.id}`,
|
|
||||||
|
|
||||||
min_size = Vector2.new(300, 300),
|
|
||||||
|
|
||||||
bind_to_close = props.destroy,
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
|
|
||||||
Size = UDim2.fromScale(1, 1),
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
tooltip {
|
|
||||||
transparency = 0.3,
|
|
||||||
visible = function()
|
|
||||||
return props.entity_hovering_over() and #props.entity_hovering_over() > 0 or false
|
|
||||||
end,
|
|
||||||
|
|
||||||
ui.typography {
|
|
||||||
automaticsize = Enum.AutomaticSize.XY,
|
|
||||||
|
|
||||||
text = function()
|
|
||||||
return props.entity_hovering_over() or ""
|
|
||||||
end,
|
|
||||||
xalignment = Enum.TextXAlignment.Left,
|
|
||||||
wrapped = true,
|
|
||||||
code = true,
|
|
||||||
|
|
||||||
{ RichText = true },
|
|
||||||
|
|
||||||
create "UIStroke" {
|
|
||||||
Thickness = 1,
|
|
||||||
Color = ui.theme.bg[-5]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
create "Highlight" {
|
|
||||||
DepthMode = Enum.HighlightDepthMode.AlwaysOnTop,
|
|
||||||
OutlineColor = Color3.new(1, 1, 1),
|
|
||||||
FillColor = ui.theme.acc[0],
|
|
||||||
FillTransparency = 0.5,
|
|
||||||
|
|
||||||
Adornee = props.hovering_over
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
VerticalFlex = Enum.UIFlexAlignment.SpaceAround,
|
|
||||||
Padding = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
Name = "Query + Pick",
|
|
||||||
Size = UDim2.new(1, 0, 0, 30),
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
HorizontalFlex = Enum.UIFlexAlignment.SpaceAround,
|
|
||||||
VerticalFlex = Enum.UIFlexAlignment.SpaceAround,
|
|
||||||
Padding = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
|
|
||||||
Name = "Query",
|
|
||||||
|
|
||||||
Size = UDim2.fromScale(0, 1),
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
create "UIFlexItem" {
|
|
||||||
FlexMode = Enum.UIFlexMode.Fill
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.textfield {
|
|
||||||
size = UDim2.new(1, 0, 0, 30),
|
|
||||||
placeholder = "Query",
|
|
||||||
|
|
||||||
code = true,
|
|
||||||
|
|
||||||
oninput = function(text)
|
|
||||||
props.validate_query(text)
|
|
||||||
end,
|
|
||||||
|
|
||||||
enter = function(text)
|
|
||||||
props.update_system_query(text)
|
|
||||||
end
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
vide.show(function()
|
|
||||||
return props.primary_entity()
|
|
||||||
end, function()
|
|
||||||
return ui.button {
|
|
||||||
size = UDim2.fromOffset(30, 30),
|
|
||||||
|
|
||||||
text = "",
|
|
||||||
|
|
||||||
activated = function()
|
|
||||||
spawn_app.spawn_app(entity, {
|
|
||||||
host = props.host,
|
|
||||||
vm = props.vm,
|
|
||||||
id = props.id,
|
|
||||||
entity = props.primary_entity()
|
|
||||||
})
|
|
||||||
end,
|
|
||||||
|
|
||||||
create "ImageLabel" {
|
|
||||||
Size = UDim2.fromOffset(24, 24),
|
|
||||||
Position = UDim2.fromScale(0.5, 0.5),
|
|
||||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
ImageColor3 = ui.theme.fg_on_bg_high[3],
|
|
||||||
|
|
||||||
Image = "rbxassetid://10723415903"
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
end),
|
|
||||||
|
|
||||||
ui.button {
|
|
||||||
size = UDim2.fromOffset(30, 30),
|
|
||||||
|
|
||||||
text = "",
|
|
||||||
|
|
||||||
accent = props.enable_pick,
|
|
||||||
|
|
||||||
activated = function()
|
|
||||||
props.enable_pick(not props.enable_pick())
|
|
||||||
end,
|
|
||||||
|
|
||||||
create "ImageLabel" {
|
|
||||||
Size = UDim2.fromOffset(24, 24),
|
|
||||||
Position = UDim2.fromScale(0.5, 0.5),
|
|
||||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
ImageColor3 = ui.theme.fg_on_bg_high[3],
|
|
||||||
|
|
||||||
Image = "rbxassetid://10734898355"
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
Size = UDim2.new(1, 0, 0, 24),
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
Visible = function()
|
|
||||||
return not props.ok() and #props.msg() > 0
|
|
||||||
end,
|
|
||||||
|
|
||||||
ui.typography {
|
|
||||||
text = props.msg
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
|
|
||||||
Size = UDim2.new(1, 0, 0, 32),
|
|
||||||
|
|
||||||
BackgroundColor3 = ui.theme.bg[2],
|
|
||||||
AutomaticSize = Enum.AutomaticSize.Y,
|
|
||||||
|
|
||||||
create "UICorner" {
|
|
||||||
CornerRadius = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
Padding = UDim.new(0, 8),
|
|
||||||
HorizontalFlex = Enum.UIFlexAlignment.SpaceAround,
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
|
||||||
Wraps = true
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.row {
|
|
||||||
spacing = UDim.new(0, 8),
|
|
||||||
alignitems = Enum.ItemLineAlignment.Center,
|
|
||||||
row_template,
|
|
||||||
|
|
||||||
ui.typography {
|
|
||||||
text = "Page:"
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.textfield {
|
|
||||||
size = UDim2.fromOffset(40, 26),
|
|
||||||
placeholder = "1",
|
|
||||||
|
|
||||||
text = function()
|
|
||||||
return page_input()
|
|
||||||
end,
|
|
||||||
|
|
||||||
oninput = page_input,
|
|
||||||
|
|
||||||
enter = function(text)
|
|
||||||
local n = tonumber(text)
|
|
||||||
|
|
||||||
if n == nil then
|
|
||||||
page_input(tostring(page()))
|
|
||||||
else
|
|
||||||
page(n)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.typography {
|
|
||||||
text = function()
|
|
||||||
return `/ {math.ceil(props.total_entities() / rows())}`
|
|
||||||
end
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
ui.row {
|
|
||||||
spacing = UDim.new(0, 8),
|
|
||||||
alignitems = Enum.ItemLineAlignment.Center,
|
|
||||||
row_template,
|
|
||||||
|
|
||||||
ui.typography {
|
|
||||||
text = "Rows:"
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.textfield {
|
|
||||||
size = UDim2.fromOffset(40, 26),
|
|
||||||
placeholder = "Rows",
|
|
||||||
|
|
||||||
text = function()
|
|
||||||
return rows_input()
|
|
||||||
end,
|
|
||||||
|
|
||||||
oninput = rows_input,
|
|
||||||
|
|
||||||
enter = function(text)
|
|
||||||
local n = tonumber(text)
|
|
||||||
|
|
||||||
if n == nil then
|
|
||||||
rows_input(tostring(rows()))
|
|
||||||
else
|
|
||||||
rows(n)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.row {
|
|
||||||
spacing = UDim.new(0, 4),
|
|
||||||
row_template,
|
|
||||||
|
|
||||||
ui.button {
|
|
||||||
size = UDim2.fromOffset(26, 26),
|
|
||||||
text = "",
|
|
||||||
|
|
||||||
accent = function()
|
|
||||||
return not props.paused()
|
|
||||||
end,
|
|
||||||
|
|
||||||
activated = function()
|
|
||||||
props.paused(not props.paused())
|
|
||||||
end,
|
|
||||||
|
|
||||||
{LayoutOrder = 10},
|
|
||||||
|
|
||||||
create "ImageLabel" {
|
|
||||||
Size = UDim2.fromOffset(24, 24),
|
|
||||||
Position = UDim2.fromScale(0.5, 0.5),
|
|
||||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
ImageColor3 = ui.theme.fg_on_bg_high[3],
|
|
||||||
|
|
||||||
Image = function()
|
|
||||||
return if props.paused() then "rbxassetid://10735024209"
|
|
||||||
else "rbxassetid://10734923214"
|
|
||||||
end
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
show(props.paused, function()
|
|
||||||
return ui.button {
|
|
||||||
size = UDim2.fromOffset(26, 26),
|
|
||||||
text = "",
|
|
||||||
|
|
||||||
activated = function()
|
|
||||||
props.refresh(true)
|
|
||||||
end,
|
|
||||||
|
|
||||||
create "ImageLabel" {
|
|
||||||
Size = UDim2.fromOffset(24, 24),
|
|
||||||
Position = UDim2.fromScale(0.5, 0.5),
|
|
||||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
ImageColor3 = ui.theme.fg_on_bg_high[3],
|
|
||||||
|
|
||||||
Image = "rbxassetid://10734933222"
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
ui.background {
|
|
||||||
size = UDim2.fromScale(1, 0),
|
|
||||||
automaticsize = Enum.AutomaticSize.Y,
|
|
||||||
|
|
||||||
create "UICorner" {
|
|
||||||
CornerRadius = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UIFlexItem" {
|
|
||||||
FlexMode = Enum.UIFlexMode.Fill
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.tablesheet {
|
|
||||||
size = UDim2.fromScale(1, 1),
|
|
||||||
suggested_column_sizes = { 0.1 },
|
|
||||||
|
|
||||||
column_sizes = function()
|
|
||||||
local t = {}
|
|
||||||
for i in props.columns() do
|
|
||||||
t[i] = 200
|
|
||||||
end
|
|
||||||
t[1] = 50
|
|
||||||
return t
|
|
||||||
end,
|
|
||||||
columns = props.columns,
|
|
||||||
|
|
||||||
read_value = function(c, r)
|
|
||||||
local column = props.columns()[c]
|
|
||||||
if not column then return "" end
|
|
||||||
return column[r] or ""
|
|
||||||
end,
|
|
||||||
|
|
||||||
on_click = function(c, r)
|
|
||||||
if not props.columns()[1][r-1] then return end
|
|
||||||
spawn_app.spawn_app(entity, {
|
|
||||||
host = props.host,
|
|
||||||
vm = props.vm,
|
|
||||||
id = props.id,
|
|
||||||
entity = props.columns()[1][r-1]
|
|
||||||
})
|
|
||||||
end,
|
|
||||||
on_click2 = function() end,
|
|
||||||
|
|
||||||
below = {
|
|
||||||
|
|
||||||
ui.padding {
|
|
||||||
x = UDim.new(0, 4),
|
|
||||||
y = UDim.new(0, 2)
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
|
||||||
Padding = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.button {
|
|
||||||
size = UDim2.fromOffset(70, 26),
|
|
||||||
text = "Previous",
|
|
||||||
|
|
||||||
activated = function()
|
|
||||||
page(page() - 1)
|
|
||||||
end,
|
|
||||||
|
|
||||||
disabled = function()
|
|
||||||
return page() == 1 or props.ok() == false
|
|
||||||
end
|
|
||||||
} :: Instance,
|
|
||||||
|
|
||||||
ui.button {
|
|
||||||
size = UDim2.fromOffset(70, 26),
|
|
||||||
text = "Next",
|
|
||||||
|
|
||||||
activated = function()
|
|
||||||
page(page() + 1)
|
|
||||||
end,
|
|
||||||
|
|
||||||
disabled = function()
|
|
||||||
local max_pages = math.max(1, math.ceil(props.total_entities() / rows()))
|
|
||||||
return page() == max_pages or props.ok() == false
|
|
||||||
end
|
|
||||||
} :: Instance,
|
|
||||||
|
|
||||||
ui.typography {
|
|
||||||
position = UDim2.new(0, 4, 0.5, 0),
|
|
||||||
anchorpoint = Vector2.new(0, 0.5),
|
|
||||||
|
|
||||||
text = function()
|
|
||||||
return `total entities: {props.total_entities()}\tfrom: {props.from()}\tuntil: {props.upto()}`
|
|
||||||
end,
|
|
||||||
} :: Instance,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
local RunService = game:GetService("RunService")
|
|
||||||
|
|
||||||
local vide = require(script.Parent.Parent.Parent.vide)
|
|
||||||
local loop = require(script.Parent.Parent.Parent.modules.loop)
|
|
||||||
local remotes = require(script.Parent.Parent.Parent.modules.remotes)
|
|
||||||
local types = require(script.Parent.Parent.Parent.modules.types)
|
|
||||||
local widget = require(script.widget)
|
|
||||||
|
|
||||||
local cleanup = vide.cleanup
|
|
||||||
|
|
||||||
local system = {
|
|
||||||
class_name = "app" :: "app",
|
|
||||||
name = "System"
|
|
||||||
}
|
|
||||||
|
|
||||||
type props = {
|
|
||||||
host: number,
|
|
||||||
vm: number,
|
|
||||||
scheduler: number,
|
|
||||||
system: number,
|
|
||||||
|
|
||||||
name: string
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function system.mount(props: props, destroy: () -> ())
|
|
||||||
|
|
||||||
local watch_id = math.random(2^31 - 1)
|
|
||||||
local recording = vide.source(false)
|
|
||||||
local watching_frame = vide.source(0)
|
|
||||||
local per_frame_data = vide.source({} :: {[number]: number})
|
|
||||||
local changes = vide.source({
|
|
||||||
component = {},
|
|
||||||
entities = {},
|
|
||||||
types = {},
|
|
||||||
values = {}
|
|
||||||
} :: types.WatchLoggedChanges)
|
|
||||||
|
|
||||||
local system_props_data = {
|
|
||||||
watch_id = watch_id,
|
|
||||||
host = props.host,
|
|
||||||
vm = props.vm,
|
|
||||||
scheduler = props.scheduler,
|
|
||||||
system = props.system,
|
|
||||||
name = props.name,
|
|
||||||
|
|
||||||
changes = changes,
|
|
||||||
recording = recording,
|
|
||||||
per_frame_data = per_frame_data,
|
|
||||||
watching_frame = watching_frame,
|
|
||||||
destroy = destroy,
|
|
||||||
}
|
|
||||||
|
|
||||||
local app_loop = loop (
|
|
||||||
"app-client-system",
|
|
||||||
system_props_data,
|
|
||||||
|
|
||||||
{i = 1},
|
|
||||||
script.systems.replicate
|
|
||||||
)
|
|
||||||
|
|
||||||
local outgoing: types.OutgoingConnector = {
|
|
||||||
host = system_props_data.host,
|
|
||||||
to_vm = system_props_data.vm
|
|
||||||
}
|
|
||||||
|
|
||||||
remotes.create_watch:fire(outgoing, props.scheduler, props.system, watch_id)
|
|
||||||
remotes.connect_watch:fire(outgoing, watch_id)
|
|
||||||
|
|
||||||
cleanup(RunService.Heartbeat:Connect(app_loop))
|
|
||||||
cleanup(function()
|
|
||||||
remotes.disconnect_watch:fire(outgoing, watch_id)
|
|
||||||
remotes.stop_watch:fire(outgoing, watch_id)
|
|
||||||
remotes.remove_watch:fire(outgoing, watch_id)
|
|
||||||
end)
|
|
||||||
|
|
||||||
return widget(system_props_data)
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
return system
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local queue = require(script.Parent.Parent.Parent.Parent.Parent.modules.queue)
|
|
||||||
local remotes = require(script.Parent.Parent.Parent.Parent.Parent.modules.remotes)
|
|
||||||
local types = require(script.Parent.Parent.Parent.Parent.Parent.modules.types)
|
|
||||||
|
|
||||||
local batch = vide.batch
|
|
||||||
|
|
||||||
type Context = {
|
|
||||||
host: "server" | Player,
|
|
||||||
vm: number,
|
|
||||||
|
|
||||||
watch_id: number,
|
|
||||||
scheduler: number,
|
|
||||||
system: number,
|
|
||||||
name: string,
|
|
||||||
|
|
||||||
recording: vide.Source<boolean>,
|
|
||||||
watching_frame: vide.Source<number>,
|
|
||||||
per_frame_data: vide.Source<{[number]: number}>,
|
|
||||||
changes: (types.WatchLoggedChanges) -> (),
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(context: Context)
|
|
||||||
|
|
||||||
local watch_id = context.watch_id
|
|
||||||
local outgoing: types.OutgoingConnector = {
|
|
||||||
host = context.host,
|
|
||||||
to_vm = context.vm
|
|
||||||
}
|
|
||||||
|
|
||||||
local recording_state_changed = false
|
|
||||||
local recording = false
|
|
||||||
|
|
||||||
vide.effect(function()
|
|
||||||
recording_state_changed = true
|
|
||||||
recording = context.recording()
|
|
||||||
end)
|
|
||||||
|
|
||||||
local watching_frame_changed = false
|
|
||||||
local watching_frame = 1
|
|
||||||
vide.effect(function()
|
|
||||||
watching_frame_changed = true
|
|
||||||
watching_frame = context.watching_frame()
|
|
||||||
end)
|
|
||||||
|
|
||||||
local receive_update_data = queue(remotes.update_watch_data)
|
|
||||||
local receive_overview = queue(remotes.update_overview)
|
|
||||||
|
|
||||||
return function()
|
|
||||||
|
|
||||||
if recording_state_changed and recording then
|
|
||||||
remotes.start_record_watch:fire(outgoing, watch_id)
|
|
||||||
recording_state_changed = false
|
|
||||||
elseif recording_state_changed and not recording then
|
|
||||||
remotes.stop_watch:fire(outgoing, watch_id)
|
|
||||||
recording_state_changed = false
|
|
||||||
end
|
|
||||||
|
|
||||||
if watching_frame_changed then
|
|
||||||
remotes.request_watch_data:fire(outgoing, watch_id, watching_frame)
|
|
||||||
watching_frame_changed = false
|
|
||||||
end
|
|
||||||
|
|
||||||
debug.profilebegin("receive update data")
|
|
||||||
batch(function()
|
|
||||||
for from, watch, frame, changes in receive_update_data:iter() do
|
|
||||||
if watch ~= watch_id then continue end
|
|
||||||
if frame ~= watching_frame then continue end
|
|
||||||
if changes == nil then
|
|
||||||
context.changes({
|
|
||||||
types = {},
|
|
||||||
entities = {},
|
|
||||||
component = {},
|
|
||||||
values = {},
|
|
||||||
worlds = {}
|
|
||||||
})
|
|
||||||
else
|
|
||||||
context.changes(changes)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
debug.profileend()
|
|
||||||
|
|
||||||
debug.profilebegin("receive overview")
|
|
||||||
batch(function()
|
|
||||||
for from, watch, frame, value in receive_overview:iter() do
|
|
||||||
if watch ~= watch_id then continue end
|
|
||||||
local data = context.per_frame_data()
|
|
||||||
if data[frame] == value then continue end
|
|
||||||
data[frame] = value
|
|
||||||
context.per_frame_data(data)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
debug.profileend()
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,235 +0,0 @@
|
||||||
local ui = require(script.Parent.Parent.Parent.Parent.ui)
|
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local types = require(script.Parent.Parent.Parent.Parent.modules.types)
|
|
||||||
local tooltip = require(script.Parent.Parent.Parent.components.tooltip)
|
|
||||||
local virtualscroller_horizontal = require(script.Parent.Parent.Parent.components.virtualscroller_horizontal)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local source = vide.source
|
|
||||||
|
|
||||||
type props = {
|
|
||||||
host: "server" | Player,
|
|
||||||
vm: number,
|
|
||||||
|
|
||||||
scheduler: number,
|
|
||||||
system: number,
|
|
||||||
name: string,
|
|
||||||
|
|
||||||
recording: vide.Source<boolean>,
|
|
||||||
watching_frame: vide.Source<number>,
|
|
||||||
per_frame_data: () -> {[number]: number},
|
|
||||||
changes: () -> types.WatchLoggedChanges,
|
|
||||||
}
|
|
||||||
|
|
||||||
local PROFILER_THICKNESS = 6
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
|
|
||||||
local is_recording = props.recording
|
|
||||||
local watching_frame = props.watching_frame
|
|
||||||
local per_frame_data = props.per_frame_data
|
|
||||||
local changes = props.changes
|
|
||||||
|
|
||||||
local function sheet_changes()
|
|
||||||
local changes = changes()
|
|
||||||
|
|
||||||
return {
|
|
||||||
{"type", unpack(changes.types)},
|
|
||||||
{"entity", unpack(changes.entities)},
|
|
||||||
{"component", unpack(changes.component)},
|
|
||||||
{"value", unpack(changes.values)}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local function max()
|
|
||||||
return math.max(1, unpack(per_frame_data()))
|
|
||||||
end
|
|
||||||
|
|
||||||
local function total_changes()
|
|
||||||
local sum = 0
|
|
||||||
for _, value in per_frame_data() do
|
|
||||||
sum += value
|
|
||||||
end
|
|
||||||
return sum
|
|
||||||
end
|
|
||||||
|
|
||||||
local hovering_over = source(false)
|
|
||||||
|
|
||||||
return ui.list {
|
|
||||||
justifycontent = Enum.UIFlexAlignment.SpaceEvenly,
|
|
||||||
spacing = UDim.new(0, 4),
|
|
||||||
|
|
||||||
ui.pane {
|
|
||||||
virtualscroller_horizontal {
|
|
||||||
item_size = PROFILER_THICKNESS,
|
|
||||||
item = function(index)
|
|
||||||
local function value()
|
|
||||||
return per_frame_data()[index()] or 0
|
|
||||||
end
|
|
||||||
|
|
||||||
return create "TextButton" {
|
|
||||||
Size = function()
|
|
||||||
return UDim2.new(0, PROFILER_THICKNESS, 1, 0)
|
|
||||||
end,
|
|
||||||
BackgroundTransparency = function()
|
|
||||||
return if hovering_over() == index() then 0.5 else 1
|
|
||||||
end,
|
|
||||||
BackgroundColor3 = ui.theme.bg[10],
|
|
||||||
|
|
||||||
MouseEnter = function()
|
|
||||||
hovering_over(index())
|
|
||||||
end,
|
|
||||||
MouseLeave = function()
|
|
||||||
if hovering_over() ~= index() then return end
|
|
||||||
hovering_over(false)
|
|
||||||
end,
|
|
||||||
Activated = function()
|
|
||||||
watching_frame(index())
|
|
||||||
end,
|
|
||||||
AutoLocalize = false,
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
Size = function()
|
|
||||||
return UDim2.fromScale(1, value() / max())
|
|
||||||
end,
|
|
||||||
Position = UDim2.fromScale(1, 1),
|
|
||||||
AnchorPoint = Vector2.new(1, 1),
|
|
||||||
BackgroundColor3 = function()
|
|
||||||
return if watching_frame() == index() then
|
|
||||||
ui.theme.acc[20]()
|
|
||||||
elseif hovering_over() == index() then
|
|
||||||
ui.theme.acc[5]()
|
|
||||||
else ui.theme.acc[0]()
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
max_items = function()
|
|
||||||
return #per_frame_data()
|
|
||||||
end,
|
|
||||||
|
|
||||||
create "UIStroke" {Color = ui.theme.bg[-3]},
|
|
||||||
|
|
||||||
{
|
|
||||||
Size = UDim2.new(1, 0, 0, 56),
|
|
||||||
HorizontalScrollBarInset = Enum.ScrollBarInset.ScrollBar,
|
|
||||||
BackgroundColor3 = ui.theme.bg[-1],
|
|
||||||
ScrollBarThickness = 6,
|
|
||||||
|
|
||||||
CanvasPosition = function()
|
|
||||||
per_frame_data()
|
|
||||||
return Vector2.new(table.maxn(per_frame_data()) * PROFILER_THICKNESS)
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.typography {
|
|
||||||
text = function()
|
|
||||||
return `Recorded {#per_frame_data()} frames and tracked {total_changes()} changes`
|
|
||||||
end
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.typography {
|
|
||||||
text = function()
|
|
||||||
return `Currently viewing frame {watching_frame()}`
|
|
||||||
end
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
tooltip {
|
|
||||||
transparency = 0,
|
|
||||||
visible = function()
|
|
||||||
return hovering_over() ~= false
|
|
||||||
end,
|
|
||||||
|
|
||||||
ui.typography {
|
|
||||||
automaticsize = Enum.AutomaticSize.XY,
|
|
||||||
|
|
||||||
text = function()
|
|
||||||
return `\z
|
|
||||||
Frame: #{hovering_over()}\n\z
|
|
||||||
Changes: {per_frame_data()[hovering_over()] or 0}`
|
|
||||||
end,
|
|
||||||
xalignment = Enum.TextXAlignment.Left,
|
|
||||||
wrapped = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.container {
|
|
||||||
Size = UDim2.fromScale(1, 0),
|
|
||||||
AutomaticSize = Enum.AutomaticSize.Y,
|
|
||||||
BackgroundColor3 = ui.theme.bg[1],
|
|
||||||
BackgroundTransparency = 0,
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
HorizontalFlex = Enum.UIFlexAlignment.SpaceEvenly,
|
|
||||||
VerticalFlex = Enum.UIFlexAlignment.Fill,
|
|
||||||
Padding = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.padding {
|
|
||||||
x = UDim.new(0, 4),
|
|
||||||
y = UDim.new(0, 4)
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.button {
|
|
||||||
size = UDim2.new(0, 80, 0, 30),
|
|
||||||
automaticsize = Enum.AutomaticSize.X,
|
|
||||||
text = function()
|
|
||||||
return if is_recording() then "Pause" else "Record"
|
|
||||||
end,
|
|
||||||
|
|
||||||
activated = function()
|
|
||||||
is_recording(not is_recording())
|
|
||||||
end
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.container {
|
|
||||||
Size = UDim2.fromScale(0, 0),
|
|
||||||
|
|
||||||
create "UIFlexItem" {
|
|
||||||
FlexMode = Enum.UIFlexMode.Fill
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.textfield {
|
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
|
||||||
text = tostring(watching_frame()),
|
|
||||||
placeholder = "frame",
|
|
||||||
|
|
||||||
enter = function(text: string)
|
|
||||||
if tonumber(text) == nil then return end
|
|
||||||
watching_frame(tonumber(text))
|
|
||||||
end
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.container {
|
|
||||||
Size = UDim2.fromScale(1, 1),
|
|
||||||
|
|
||||||
create "UIFlexItem" {
|
|
||||||
FlexMode = Enum.UIFlexMode.Fill
|
|
||||||
},
|
|
||||||
|
|
||||||
ui.tablesheet {
|
|
||||||
size = UDim2.fromScale(1, 1),
|
|
||||||
|
|
||||||
column_sizes = source {100, 80, 100, 200},
|
|
||||||
read_value = function(column, row)
|
|
||||||
local v = sheet_changes()[column][row]
|
|
||||||
return if v == false then "" else v
|
|
||||||
end,
|
|
||||||
|
|
||||||
on_click = function() end,
|
|
||||||
on_click2 = function() end,
|
|
||||||
|
|
||||||
columns = sheet_changes
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
local ui = require(script.Parent.Parent.Parent.Parent.ui)
|
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local types = require(script.Parent.Parent.Parent.Parent.modules.types)
|
|
||||||
local watch_tracker = require(script.Parent.watch_tracker)
|
|
||||||
|
|
||||||
type props = {
|
|
||||||
host: "server" | Player,
|
|
||||||
vm: number,
|
|
||||||
|
|
||||||
scheduler: number,
|
|
||||||
system: number,
|
|
||||||
name: string,
|
|
||||||
|
|
||||||
destroy: () -> (),
|
|
||||||
|
|
||||||
recording: vide.Source<boolean>,
|
|
||||||
watching_frame: vide.Source<number>,
|
|
||||||
per_frame_data: () -> {[number]: number},
|
|
||||||
changes: () -> types.WatchLoggedChanges,
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
|
|
||||||
return ui.widget {
|
|
||||||
title = `system - {props.name}`,
|
|
||||||
subtitle = `host: {props.host} vm: {props.vm} scheduler: {props.scheduler} system: {props.system}`,
|
|
||||||
bind_to_close = props.destroy,
|
|
||||||
size = Vector2.new(350, 400),
|
|
||||||
min_size = Vector2.new(300, 300),
|
|
||||||
|
|
||||||
watch_tracker(props)
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
local RunService = game:GetService("RunService")
|
|
||||||
local UserInputService = game:GetService("UserInputService")
|
|
||||||
|
|
||||||
local ui = require(script.Parent.Parent.Parent.ui)
|
|
||||||
local vide = require(script.Parent.Parent.Parent.vide)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local source = vide.source
|
|
||||||
local cleanup = vide.cleanup
|
|
||||||
|
|
||||||
type props = {
|
|
||||||
visible: boolean | () -> boolean,
|
|
||||||
transparency: number? | () -> number,
|
|
||||||
[number]: any
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
|
|
||||||
local mouse_location = source(Vector2.zero)
|
|
||||||
|
|
||||||
cleanup(RunService.PreRender:Connect(function()
|
|
||||||
mouse_location(UserInputService:GetMouseLocation())
|
|
||||||
end))
|
|
||||||
|
|
||||||
return create "ScreenGui" {
|
|
||||||
Name = "Mouse Hover",
|
|
||||||
IgnoreGuiInset = true,
|
|
||||||
DisplayOrder = 1e9,
|
|
||||||
Enabled = props.visible,
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
Position = function()
|
|
||||||
return UDim2.fromOffset(
|
|
||||||
mouse_location().X + 24,
|
|
||||||
mouse_location().Y + 24
|
|
||||||
)
|
|
||||||
end,
|
|
||||||
Size = UDim2.fromOffset(400, 0),
|
|
||||||
AutomaticSize = Enum.AutomaticSize.XY,
|
|
||||||
BackgroundColor3 = ui.theme.bg[0],
|
|
||||||
BackgroundTransparency = props.transparency or 0.5,
|
|
||||||
|
|
||||||
ui.padding {},
|
|
||||||
|
|
||||||
create "UICorner" {
|
|
||||||
CornerRadius = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UIStroke" {
|
|
||||||
Color = ui.theme.bg[-10],
|
|
||||||
Thickness = 2,
|
|
||||||
Transparency = 0.8
|
|
||||||
},
|
|
||||||
|
|
||||||
unpack(props)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,185 +0,0 @@
|
||||||
local ui = require(script.Parent.Parent.Parent.ui)
|
|
||||||
local vide = require(script.Parent.Parent.Parent.vide)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local source = vide.source
|
|
||||||
local values = vide.values
|
|
||||||
local changed = vide.changed
|
|
||||||
local effect = vide.effect
|
|
||||||
local untrack = vide.untrack
|
|
||||||
|
|
||||||
type can<T> = T | () -> T
|
|
||||||
type props = {
|
|
||||||
|
|
||||||
size: can<UDim2>?,
|
|
||||||
position: can<UDim2>?,
|
|
||||||
anchorpoint: can<UDim2>?,
|
|
||||||
|
|
||||||
--- streams in items. when index is -1, should expect to be unused
|
|
||||||
item: (index: () -> number) -> Instance,
|
|
||||||
--- streams in separators. when index is -1, should expect to be unused
|
|
||||||
separator: ((index: () -> number) -> Instance)?,
|
|
||||||
|
|
||||||
item_size: number,
|
|
||||||
separator_size: number?,
|
|
||||||
|
|
||||||
max_items: (() -> number)?,
|
|
||||||
|
|
||||||
[number]: any
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
|
|
||||||
local items = source({} :: {vide.Source<number>})
|
|
||||||
|
|
||||||
local absolute_size = source(Vector2.zero)
|
|
||||||
local canvas_position = source(Vector2.zero)
|
|
||||||
|
|
||||||
local item_size = props.item_size
|
|
||||||
local separator_size = props.separator_size or 0
|
|
||||||
|
|
||||||
local item = props.item
|
|
||||||
local separator = props.separator
|
|
||||||
|
|
||||||
local OVERFLOW_ONE_SIDE = 4
|
|
||||||
|
|
||||||
effect(function()
|
|
||||||
local absolute_size = absolute_size()
|
|
||||||
local canvas_position = canvas_position()
|
|
||||||
|
|
||||||
local child_size = item_size + separator_size
|
|
||||||
local total_required = math.ceil(absolute_size.X / child_size) + OVERFLOW_ONE_SIDE * 2
|
|
||||||
local sources = untrack(items)
|
|
||||||
|
|
||||||
local min_index = math.floor(canvas_position.X / child_size)
|
|
||||||
local max_index = math.ceil((canvas_position.X + absolute_size.X) / child_size)
|
|
||||||
|
|
||||||
local max_items = math.huge
|
|
||||||
if props.max_items then
|
|
||||||
max_items = props.max_items()
|
|
||||||
end
|
|
||||||
|
|
||||||
untrack(function()
|
|
||||||
-- mark any sources out of range as unused
|
|
||||||
local unused = {}
|
|
||||||
|
|
||||||
for i, s in sources do
|
|
||||||
local index = s()
|
|
||||||
|
|
||||||
if
|
|
||||||
index >= math.max(min_index, 1)
|
|
||||||
and index <= math.min(max_index, max_items)
|
|
||||||
then continue end
|
|
||||||
unused[i] = true
|
|
||||||
s(-1)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- add sources necessary
|
|
||||||
if #sources < total_required then
|
|
||||||
for i = #sources + 1, total_required do
|
|
||||||
sources[i] = source(-1)
|
|
||||||
unused[i] = true
|
|
||||||
end
|
|
||||||
items(sources)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- update indexes of any sources that went unused
|
|
||||||
local did_not_render = {}
|
|
||||||
|
|
||||||
for i = math.max(min_index, 1), math.min(max_index, max_items) do
|
|
||||||
did_not_render[i] = true
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
for _, s in sources do
|
|
||||||
did_not_render[s()] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
for index in unused do
|
|
||||||
local s = sources[index]
|
|
||||||
local key = next(did_not_render)
|
|
||||||
if not key then break end
|
|
||||||
s(key)
|
|
||||||
did_not_render[key] = nil
|
|
||||||
unused[index] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
if next(did_not_render) then warn("missing source!", next(did_not_render)) end
|
|
||||||
|
|
||||||
-- remove unnecessary sources
|
|
||||||
if #sources > total_required then
|
|
||||||
for i = #sources, 1, -1 do
|
|
||||||
if unused[i] then
|
|
||||||
table.remove(sources, i)
|
|
||||||
end
|
|
||||||
unused[i] = nil
|
|
||||||
if #sources < total_required then break end
|
|
||||||
end
|
|
||||||
items(sources)
|
|
||||||
end
|
|
||||||
|
|
||||||
end)
|
|
||||||
|
|
||||||
end)
|
|
||||||
|
|
||||||
return create "ScrollingFrame" {
|
|
||||||
|
|
||||||
Size = props.size or UDim2.fromScale(1, 1),
|
|
||||||
Position = props.position,
|
|
||||||
AnchorPoint = props.anchorpoint,
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
CanvasSize = function()
|
|
||||||
if props.max_items then
|
|
||||||
return UDim2.fromOffset(props.max_items() * (item_size + separator_size), 0)
|
|
||||||
else
|
|
||||||
local absolute_size = absolute_size()
|
|
||||||
local canvas_position = canvas_position()
|
|
||||||
local child_size = item_size + separator_size
|
|
||||||
local max_index = math.ceil((canvas_position.X + absolute_size.X) / child_size) + OVERFLOW_ONE_SIDE
|
|
||||||
return UDim2.fromOffset(max_index * child_size, 0)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
values(items, function(index)
|
|
||||||
return create "Frame" {
|
|
||||||
Name = index,
|
|
||||||
|
|
||||||
Position = function()
|
|
||||||
if index() == -1 then UDim2.fromOffset(0, -1000) end
|
|
||||||
return UDim2.fromOffset(
|
|
||||||
(item_size + separator_size) * (index() - 1),
|
|
||||||
0
|
|
||||||
)
|
|
||||||
end,
|
|
||||||
Size = UDim2.new(0, item_size + separator_size, 1, 0),
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
ui.container {
|
|
||||||
Name = "Item",
|
|
||||||
|
|
||||||
item(index),
|
|
||||||
},
|
|
||||||
|
|
||||||
if separator then
|
|
||||||
ui.container {
|
|
||||||
Name = "Separator",
|
|
||||||
|
|
||||||
separator(index)
|
|
||||||
}
|
|
||||||
else nil,
|
|
||||||
|
|
||||||
}
|
|
||||||
end),
|
|
||||||
|
|
||||||
changed("AbsoluteSize", absolute_size),
|
|
||||||
changed("CanvasPosition", canvas_position),
|
|
||||||
|
|
||||||
unpack(props),
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
local entity = require(script.apps.entity)
|
|
||||||
local home = require(script.apps.home)
|
|
||||||
local overview_scheduler = require(script.apps.overview_scheduler)
|
|
||||||
local registry = require(script.apps.registry)
|
|
||||||
local spawn_app = require(script.spawn_app)
|
|
||||||
|
|
||||||
return {
|
|
||||||
|
|
||||||
apps = {
|
|
||||||
home = home,
|
|
||||||
entity = entity,
|
|
||||||
scheduler = overview_scheduler,
|
|
||||||
registry = registry,
|
|
||||||
},
|
|
||||||
|
|
||||||
spawn_app = spawn_app.spawn_app,
|
|
||||||
unmount_all = spawn_app.unmount_all
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
local Players = game:GetService("Players")
|
|
||||||
|
|
||||||
local vide = require(script.Parent.Parent.vide)
|
|
||||||
local types = require(script.Parent.Parent.modules.types)
|
|
||||||
|
|
||||||
local destroy_fn = {}
|
|
||||||
|
|
||||||
local function unmount_all()
|
|
||||||
for destroy in destroy_fn do
|
|
||||||
destroy()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function spawn_app<T>(app: types.Application<T>, props: T): () -> ()
|
|
||||||
return vide.root(function(destroy)
|
|
||||||
|
|
||||||
local destroy = function()
|
|
||||||
destroy_fn[destroy] = nil
|
|
||||||
destroy()
|
|
||||||
end
|
|
||||||
|
|
||||||
local application = app.mount(props, destroy)
|
|
||||||
application.Parent = Players.LocalPlayer.PlayerGui
|
|
||||||
|
|
||||||
vide.cleanup(application)
|
|
||||||
|
|
||||||
destroy_fn[destroy] = true
|
|
||||||
|
|
||||||
return destroy
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
unmount_all = unmount_all,
|
|
||||||
spawn_app = spawn_app
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
local RunService = game:GetService("RunService")
|
|
||||||
|
|
||||||
local jecs = require("@jecs")
|
|
||||||
local jabby = require("@modules/Jabby/module")
|
|
||||||
|
|
||||||
local world = jecs.world()
|
|
||||||
|
|
||||||
jabby.set_check_function(function()
|
|
||||||
return true
|
|
||||||
end)
|
|
||||||
|
|
||||||
local scheduler = jabby.scheduler.create()
|
|
||||||
|
|
||||||
jabby.register({
|
|
||||||
applet = jabby.applets.scheduler,
|
|
||||||
name = "Example Scheduler",
|
|
||||||
configuration = {
|
|
||||||
scheduler = scheduler,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
jabby.register({
|
|
||||||
applet = jabby.applets.world,
|
|
||||||
name = "Example World",
|
|
||||||
configuration = {
|
|
||||||
world = world,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
local system_id = scheduler:register_system({
|
|
||||||
name = "example_system",
|
|
||||||
module = script,
|
|
||||||
})
|
|
||||||
|
|
||||||
local function example_system(_world: jecs.World, dt: number)
|
|
||||||
return dt
|
|
||||||
end
|
|
||||||
|
|
||||||
if RunService:IsClient() then
|
|
||||||
local client = jabby.obtain_client()
|
|
||||||
client.spawn_app(client.apps.home, nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
RunService.Heartbeat:Connect(function(dt)
|
|
||||||
scheduler:run(system_id, example_system, world, dt)
|
|
||||||
end)
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,60 +0,0 @@
|
||||||
local jecs = require(script.Parent.jecs)
|
|
||||||
local traffic_check = require(script.Parent.modules.traffic_check)
|
|
||||||
local types = require(script.Parent.modules.types)
|
|
||||||
local vm_id = require(script.Parent.modules.vm_id)
|
|
||||||
local server = require(script.Parent.server)
|
|
||||||
local public = require(script.Parent.server.public)
|
|
||||||
local scheduler = require(script.Parent.server.scheduler)
|
|
||||||
|
|
||||||
type Applet<T> = {
|
|
||||||
add_to_public: (name: string, config: T) -> ()
|
|
||||||
}
|
|
||||||
|
|
||||||
local world_applet = {
|
|
||||||
add_to_public = function(
|
|
||||||
name: string, config: { world: jecs.World, entities: {[Instance]: jecs.Entity<any>}?, get_entity_from_part: ((part: BasePart) -> (jecs.Entity<any>, Part?))? }
|
|
||||||
)
|
|
||||||
public.updated = true
|
|
||||||
table.insert(public, {
|
|
||||||
class_name = "World",
|
|
||||||
name = name,
|
|
||||||
world = config.world,
|
|
||||||
entities = config.entities,
|
|
||||||
get_entity_from_part = config.get_entity_from_part
|
|
||||||
})
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
local scheduler_applet = {
|
|
||||||
add_to_public = function(
|
|
||||||
name: string, config: { scheduler: types.Scheduler }
|
|
||||||
)
|
|
||||||
public.updated = true
|
|
||||||
config.scheduler.name = name
|
|
||||||
table.insert(public, config.scheduler)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
set_check_function = function(callback: (Player) -> boolean)
|
|
||||||
traffic_check.can_use_jabby = callback
|
|
||||||
end,
|
|
||||||
|
|
||||||
obtain_client = function()
|
|
||||||
return require(script.Parent.client)
|
|
||||||
end,
|
|
||||||
|
|
||||||
vm_id = vm_id,
|
|
||||||
scheduler = scheduler,
|
|
||||||
|
|
||||||
broadcast_server = server.broadcast,
|
|
||||||
|
|
||||||
applets = {
|
|
||||||
world = world_applet,
|
|
||||||
scheduler = scheduler_applet
|
|
||||||
},
|
|
||||||
|
|
||||||
register = function<T>(info: { name: string, applet: Applet<T>, configuration: T })
|
|
||||||
info.applet.add_to_public(info.name, info.configuration)
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
local function average(n: {number})
|
|
||||||
local sum = 0
|
|
||||||
for i, v in n do
|
|
||||||
sum += v
|
|
||||||
end
|
|
||||||
return sum / #n
|
|
||||||
end
|
|
||||||
|
|
||||||
return average
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
local function convert_units(unit: string, value: number): (string)
|
|
||||||
local s = math.sign(value)
|
|
||||||
value = math.abs(value)
|
|
||||||
local prefixes = {
|
|
||||||
[4] = "T",
|
|
||||||
[3] ="G",
|
|
||||||
[2] ="M",
|
|
||||||
[1] = "k",
|
|
||||||
[0] = " ",
|
|
||||||
[-1] = "m",
|
|
||||||
[-2] = "u",
|
|
||||||
[-3] = "n",
|
|
||||||
[-4] = "p"
|
|
||||||
}
|
|
||||||
|
|
||||||
local order = 0
|
|
||||||
|
|
||||||
while value >= 1000 do
|
|
||||||
order += 1
|
|
||||||
value /= 1000
|
|
||||||
end
|
|
||||||
|
|
||||||
while value ~= 0 and value < 1 do
|
|
||||||
order -= 1
|
|
||||||
value *= 1000
|
|
||||||
end
|
|
||||||
|
|
||||||
if value >= 100 then
|
|
||||||
value = math.floor(value)
|
|
||||||
elseif value >= 10 then
|
|
||||||
value = math.floor(value * 1e1) / 1e1
|
|
||||||
elseif value >= 1 then
|
|
||||||
value = math.floor(value * 1e2) / 1e2
|
|
||||||
end
|
|
||||||
|
|
||||||
return value * s .. prefixes[order] .. unit
|
|
||||||
end
|
|
||||||
|
|
||||||
return convert_units
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
local types = require(script.Parent.types)
|
|
||||||
|
|
||||||
return function(connector: types.IncomingConnector | types.OutgoingConnector)
|
|
||||||
return `{connector.host}\0{connector.from_vm or connector.to_vm}`
|
|
||||||
end
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,107 +0,0 @@
|
||||||
local scheduler = require(script.Parent.Parent.server.scheduler)
|
|
||||||
type Array<T> = { T }
|
|
||||||
|
|
||||||
export type System = (any, number) -> ...(any, number) -> ()
|
|
||||||
|
|
||||||
type GroupInfo = { i: number?, o: number? }
|
|
||||||
|
|
||||||
type SystemGroup = {
|
|
||||||
interval: number,
|
|
||||||
offset: number,
|
|
||||||
dt: number,
|
|
||||||
[number]: {
|
|
||||||
id: number,
|
|
||||||
name: string,
|
|
||||||
type: number,
|
|
||||||
fn: (...any) -> ...any
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
local function loop_create(name: string, data: any, ...: ModuleScript | () -> () | GroupInfo)
|
|
||||||
local jabby_scheduler = scheduler.create(name)
|
|
||||||
|
|
||||||
local groups = {} :: Array<SystemGroup>
|
|
||||||
|
|
||||||
local current_group: SystemGroup?
|
|
||||||
|
|
||||||
local function process_systems(array: Array<any>)
|
|
||||||
for i, v in array do
|
|
||||||
if type(v) == "table" then
|
|
||||||
if v.i then
|
|
||||||
if current_group then
|
|
||||||
table.insert(groups, current_group)
|
|
||||||
end
|
|
||||||
|
|
||||||
current_group = {
|
|
||||||
interval = v.i or 1,
|
|
||||||
offset = v.o or 0,
|
|
||||||
dt = 0
|
|
||||||
}
|
|
||||||
else
|
|
||||||
process_systems(v)
|
|
||||||
end
|
|
||||||
elseif type(v) == "function" then
|
|
||||||
assert(current_group)
|
|
||||||
|
|
||||||
table.insert(current_group, {
|
|
||||||
id = jabby_scheduler:register_system(),
|
|
||||||
name = "UNNAMED",
|
|
||||||
type = 0,
|
|
||||||
fn = v
|
|
||||||
})
|
|
||||||
else
|
|
||||||
assert(current_group)
|
|
||||||
|
|
||||||
local fn = (require :: any)(v) :: System
|
|
||||||
local fn2 = fn(data, 0)
|
|
||||||
|
|
||||||
table.insert(current_group, {
|
|
||||||
id = jabby_scheduler:register_system({name = `{v.Name}`}),
|
|
||||||
name = v.Name,
|
|
||||||
type = fn2 and 1 or 0,
|
|
||||||
fn = fn2 or fn
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
process_systems { ... }
|
|
||||||
|
|
||||||
assert(current_group)
|
|
||||||
table.insert(groups, current_group)
|
|
||||||
current_group = nil
|
|
||||||
|
|
||||||
local frame_count = 0
|
|
||||||
|
|
||||||
return function(dt)
|
|
||||||
frame_count += 1
|
|
||||||
|
|
||||||
debug.profilebegin("ECS LOOP")
|
|
||||||
|
|
||||||
for _, group in groups do
|
|
||||||
group.dt += dt
|
|
||||||
|
|
||||||
if frame_count % group.interval == group.offset then
|
|
||||||
for _, system in ipairs(group) do
|
|
||||||
debug.setmemorycategory(system.name)
|
|
||||||
debug.profilebegin(system.name)
|
|
||||||
|
|
||||||
if system.type == 0 then
|
|
||||||
jabby_scheduler:run(system.id, system.fn, data, group.dt)
|
|
||||||
else
|
|
||||||
jabby_scheduler:run(system.id, system.fn, group.dt)
|
|
||||||
end
|
|
||||||
|
|
||||||
debug.profileend()
|
|
||||||
end
|
|
||||||
|
|
||||||
group.dt = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
debug.resetmemorycategory()
|
|
||||||
debug.profileend()
|
|
||||||
end, jabby_scheduler
|
|
||||||
end
|
|
||||||
|
|
||||||
return loop_create
|
|
||||||
|
|
@ -1,202 +0,0 @@
|
||||||
--[[
|
|
||||||
|
|
||||||
net is a utility library designed to handle connections to other actors and
|
|
||||||
the server for me.
|
|
||||||
|
|
||||||
]]
|
|
||||||
|
|
||||||
local Players = game:GetService("Players")
|
|
||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
||||||
local RunService = game:GetService("RunService")
|
|
||||||
|
|
||||||
local signal = require(script.Parent.signal)
|
|
||||||
local vm_id = require(script.Parent.vm_id)
|
|
||||||
local traffic_check = require(script.Parent.traffic_check)
|
|
||||||
local types = require(script.Parent.types)
|
|
||||||
|
|
||||||
local local_host: "server" | Player
|
|
||||||
local MANAGER_VM = 0
|
|
||||||
|
|
||||||
if RunService:IsServer() then
|
|
||||||
local_host = "server"
|
|
||||||
else
|
|
||||||
local_host = Players.LocalPlayer
|
|
||||||
end
|
|
||||||
|
|
||||||
local function tincoming_connector(t: any): boolean
|
|
||||||
if typeof(t) ~= "table" then return false end
|
|
||||||
if not (t.host == "server" or (typeof(t.host) == "Instance" and t.host:IsA("Player"))) then return false end
|
|
||||||
if typeof(t.from_vm) ~= "number" then return false end
|
|
||||||
if t.to_vm ~= nil and typeof(t.to_vm) ~= "number" then return false end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local NAME = "JABBY_REMOTES"
|
|
||||||
local folder: Instance
|
|
||||||
local created_folder = false
|
|
||||||
if RunService:IsServer() then
|
|
||||||
local existing = ReplicatedStorage:FindFirstChild(NAME)
|
|
||||||
if existing then
|
|
||||||
folder = existing
|
|
||||||
else
|
|
||||||
created_folder = true
|
|
||||||
warn("\z
|
|
||||||
There's a bug with jabby that sometimes causes the JABBY_REMOTES folder to not replicate very rarely. \z
|
|
||||||
Unfortunately, I still haven't thought of a fix yet -,- so please instead clone the JABBY_REMOTES \z
|
|
||||||
folder into your game which should stop it. make sure to set archivable to true!")
|
|
||||||
folder = Instance.new("Folder")
|
|
||||||
folder.Name = NAME
|
|
||||||
folder.Archivable = false
|
|
||||||
folder.Parent = ReplicatedStorage
|
|
||||||
end
|
|
||||||
else
|
|
||||||
folder = ReplicatedStorage:WaitForChild(NAME)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_remote_event(name: string, unreliable: boolean?): RemoteEvent & { actor: BindableEvent, peer: RemoteEvent }
|
|
||||||
if RunService:IsServer() then
|
|
||||||
return folder:FindFirstChild(name) :: RemoteEvent & { actor: BindableEvent }
|
|
||||||
or (function()
|
|
||||||
if not created_folder then
|
|
||||||
warn(`btw, you are missing {name} from the JABBY_REMOTES folder`)
|
|
||||||
end
|
|
||||||
|
|
||||||
local remote = Instance.new(if unreliable then "UnreliableRemoteEvent" else "RemoteEvent")
|
|
||||||
remote.Name = name
|
|
||||||
remote.Parent = folder
|
|
||||||
|
|
||||||
local fire_actor = Instance.new("BindableEvent")
|
|
||||||
fire_actor.Name = "actor"
|
|
||||||
fire_actor.Parent = remote
|
|
||||||
|
|
||||||
local peer = Instance.new("RemoteEvent")
|
|
||||||
peer.Name = "peer"
|
|
||||||
peer.Parent = remote
|
|
||||||
|
|
||||||
return remote :: RemoteEvent & { actor: BindableEvent, peer: RemoteEvent }
|
|
||||||
end)()
|
|
||||||
else
|
|
||||||
return folder:WaitForChild(name) :: RemoteEvent & { actor: BindableEvent }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function create_event<T...>(name: string, unreliable: boolean?, do_not_block_traffic: boolean?)
|
|
||||||
local remote = get_remote_event(name, unreliable)
|
|
||||||
local on_event_fire, fire = signal()
|
|
||||||
|
|
||||||
local event = {
|
|
||||||
type = "event",
|
|
||||||
|
|
||||||
fire = function(_, connector: types.OutgoingConnector, ...)
|
|
||||||
--- if the host is within this vm, we can fire it straight to
|
|
||||||
if not traffic_check.check(local_host, connector.host, true) then return end
|
|
||||||
|
|
||||||
-- same host, same vm.
|
|
||||||
if
|
|
||||||
connector.host == local_host
|
|
||||||
and connector.to_vm == vm_id
|
|
||||||
then
|
|
||||||
|
|
||||||
local incoming = {
|
|
||||||
host = local_host,
|
|
||||||
from_vm = vm_id,
|
|
||||||
to_vm = connector.to_vm
|
|
||||||
}
|
|
||||||
|
|
||||||
fire(incoming, ...)
|
|
||||||
--- if the host is the same, but in a separate actor
|
|
||||||
--- we have to fire the actor
|
|
||||||
elseif
|
|
||||||
connector.host == local_host
|
|
||||||
and connector.to_vm ~= vm_id
|
|
||||||
then
|
|
||||||
local incoming = {
|
|
||||||
host = local_host,
|
|
||||||
from_vm = vm_id,
|
|
||||||
to_vm = connector.to_vm
|
|
||||||
}
|
|
||||||
|
|
||||||
remote.actor:Fire(incoming, ...)
|
|
||||||
--- we need to fire the server
|
|
||||||
elseif connector.host == "server" then
|
|
||||||
local incoming = {
|
|
||||||
host = "server",
|
|
||||||
from_vm = vm_id,
|
|
||||||
to_vm = connector.to_vm
|
|
||||||
}
|
|
||||||
|
|
||||||
remote:FireServer(incoming, ...)
|
|
||||||
--- we need to fire the client
|
|
||||||
elseif local_host == "server" then
|
|
||||||
local incoming = {
|
|
||||||
host = "server",
|
|
||||||
from_vm = vm_id,
|
|
||||||
to_vm = connector.to_vm
|
|
||||||
}
|
|
||||||
|
|
||||||
remote:FireClient(connector.host, incoming, ...)
|
|
||||||
--- we need to tell the server to redirect this to the client
|
|
||||||
else
|
|
||||||
local incoming = {
|
|
||||||
host = connector.host,
|
|
||||||
from_vm = vm_id,
|
|
||||||
to_vm = connector.to_vm
|
|
||||||
}
|
|
||||||
|
|
||||||
remote:FireServer(incoming, ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
end,
|
|
||||||
|
|
||||||
connect = function(_, callback: (types.IncomingConnector, T...) -> ())
|
|
||||||
return on_event_fire:connect(callback :: any)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
if RunService:IsServer() then
|
|
||||||
remote.OnServerEvent:Connect(function(player, target: types.IncomingConnector, ...)
|
|
||||||
--- check if the player is allowed to send this
|
|
||||||
if not do_not_block_traffic and not traffic_check.check(player, target.host) then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
--- check if its a proper connector
|
|
||||||
if not tincoming_connector(target) then return end
|
|
||||||
|
|
||||||
if target.host == "server" and (target.to_vm == vm_id or target.to_vm == nil) then
|
|
||||||
target.host = player
|
|
||||||
fire(target, ...)
|
|
||||||
elseif target.host ~= "server" and vm_id == MANAGER_VM then
|
|
||||||
local to = target.host
|
|
||||||
target.host = player
|
|
||||||
remote:FireClient(
|
|
||||||
to,
|
|
||||||
target,
|
|
||||||
...
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
else
|
|
||||||
remote.OnClientEvent:Connect(function(incoming: types.IncomingConnector, ...)
|
|
||||||
-- print("receive", remote.Name, "from", incoming.host)
|
|
||||||
if tincoming_connector(incoming) == false then return end
|
|
||||||
if incoming.to_vm ~= vm_id and incoming.to_vm ~= nil then return end
|
|
||||||
traffic_check._whitelist(local_host, incoming.host)
|
|
||||||
|
|
||||||
fire(incoming, ...)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
remote:WaitForChild("actor").Event:Connect(function(incoming: types.IncomingConnector, ...)
|
|
||||||
if incoming.to_vm ~= vm_id and incoming.to_vm ~= nil then return end
|
|
||||||
fire(incoming, ...)
|
|
||||||
end)
|
|
||||||
|
|
||||||
return (event :: any) :: types.NetEvent<T...>
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
create_event = create_event,
|
|
||||||
local_host = local_host
|
|
||||||
}
|
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
--- Licensed under MIT from centau_ri
|
|
||||||
export type Queue<T...> = typeof(setmetatable(
|
|
||||||
{} :: {
|
|
||||||
add: (self: Queue<T...>, T...) -> (),
|
|
||||||
clear: (self: Queue<T...>) -> (),
|
|
||||||
iter: (self: Queue<T...>) -> () -> T...,
|
|
||||||
},
|
|
||||||
{} :: {
|
|
||||||
__len: (self: Queue<T...>) -> number,
|
|
||||||
__iter: (self: Queue<T...>) -> () -> T...,
|
|
||||||
}
|
|
||||||
))
|
|
||||||
|
|
||||||
type Array<T> = { T }
|
|
||||||
|
|
||||||
local Queue = {}
|
|
||||||
do
|
|
||||||
Queue.__index = Queue
|
|
||||||
|
|
||||||
type _Queue = Queue<...any> & {
|
|
||||||
size: number,
|
|
||||||
columns: Array<Array<unknown>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
function Queue.new<T...>(): Queue<T...>
|
|
||||||
local self: _Queue = setmetatable({
|
|
||||||
size = 0,
|
|
||||||
columns = {},
|
|
||||||
}, Queue) :: any
|
|
||||||
|
|
||||||
setmetatable(self.columns, {
|
|
||||||
__index = function(columns: Array<Array<unknown>>, idx: number)
|
|
||||||
columns[idx] = {}
|
|
||||||
return columns[idx]
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
return self :: Queue<T...>
|
|
||||||
end
|
|
||||||
|
|
||||||
function Queue.add(self: _Queue, ...: unknown)
|
|
||||||
-- iteration will stop if first value is `nil`
|
|
||||||
assert((...) ~= nil, "first argument cannot be nil")
|
|
||||||
|
|
||||||
local columns = self.columns
|
|
||||||
local n = self.size + 1
|
|
||||||
self.size = n
|
|
||||||
|
|
||||||
for i = 1, select("#", ...) do
|
|
||||||
columns[i][n] = select(i, ...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Queue.clear(self: _Queue)
|
|
||||||
self.size = 0
|
|
||||||
for _, column in next, self.columns do
|
|
||||||
table.clear(column)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function iter(self: _Queue)
|
|
||||||
local columns = self.columns
|
|
||||||
local n = self.size
|
|
||||||
local i = 0
|
|
||||||
|
|
||||||
if #columns <= 1 then
|
|
||||||
local column = columns[1]
|
|
||||||
return function()
|
|
||||||
i += 1
|
|
||||||
local value = column[i]
|
|
||||||
if i == n then self:clear() end
|
|
||||||
return value
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local tuple = table.create(#columns)
|
|
||||||
return function()
|
|
||||||
i += 1
|
|
||||||
for ci, column in next, columns do
|
|
||||||
tuple[ci] = column[i]
|
|
||||||
end
|
|
||||||
if i == n then self:clear() end
|
|
||||||
return unpack(tuple)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Queue.iter = iter
|
|
||||||
Queue.__iter = iter
|
|
||||||
|
|
||||||
function Queue.__len(self: _Queue)
|
|
||||||
return self.size
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
type ISignal<T...> = {
|
|
||||||
connect: (self: any, listener: (T...) -> ()) -> (),
|
|
||||||
} | {
|
|
||||||
Connect: (self: any, listener: (T...) -> ()) -> (),
|
|
||||||
}
|
|
||||||
|
|
||||||
local queue_create = function<T...>(signal: ISignal<T...>?): Queue<T...>
|
|
||||||
local queue = Queue.new()
|
|
||||||
|
|
||||||
if signal then
|
|
||||||
local connector = (signal :: any).connect or (signal :: any).Connect
|
|
||||||
assert(connector, "signal has no member `connect()`")
|
|
||||||
connector(signal, function(...)
|
|
||||||
queue:add(...)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
return queue
|
|
||||||
end :: (<T...>() -> Queue<T...>) & (<T...>(signal: ISignal<T...>) -> Queue<T...>)
|
|
||||||
|
|
||||||
return queue_create
|
|
||||||
|
|
@ -1,359 +0,0 @@
|
||||||
local jecs = require(script.Parent.Parent.jecs)
|
|
||||||
local net = require(script.Parent.net)
|
|
||||||
local types = require(script.Parent.types)
|
|
||||||
|
|
||||||
--todo: redo this file
|
|
||||||
type Query = {
|
|
||||||
include: { jecs.Entity<any> },
|
|
||||||
exclude: { jecs.Entity<any> },
|
|
||||||
with: { jecs.Entity<any> },
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
|
|
||||||
--[=[
|
|
||||||
Broadcasts to clients that a new server has been registered.
|
|
||||||
Accepts no params.
|
|
||||||
]=]
|
|
||||||
new_server_registered = net.create_event("server_registered", false, true)
|
|
||||||
:: types.NetEvent<>,
|
|
||||||
|
|
||||||
--[=[
|
|
||||||
Pings all servers and make them respond with new_server_registered
|
|
||||||
]=]
|
|
||||||
ping = net.create_event("ping", false, true)
|
|
||||||
:: types.NetEvent<>,
|
|
||||||
|
|
||||||
--[=[
|
|
||||||
Broadcasts to servers that a new client has been registered
|
|
||||||
Accepts no params.
|
|
||||||
]=]
|
|
||||||
bind_to_server_core = net.create_event("client_registered")
|
|
||||||
:: types.NetEvent<>,
|
|
||||||
|
|
||||||
--[=[
|
|
||||||
Sends a update to a client about a new server
|
|
||||||
]=]
|
|
||||||
update_server_data =
|
|
||||||
net.create_event("update_server_data")
|
|
||||||
:: types.NetEvent<{
|
|
||||||
worlds: {{id: number, name: string}},
|
|
||||||
schedulers: {{id: number, name: string}}
|
|
||||||
}>,
|
|
||||||
|
|
||||||
--[=[
|
|
||||||
The client will use this to send the mouse pointer to the server
|
|
||||||
]=]
|
|
||||||
send_mouse_pointer =
|
|
||||||
net.create_event("send_mouse_pointer")
|
|
||||||
:: types.NetEvent<number, Vector3, Vector3>,
|
|
||||||
|
|
||||||
--[[
|
|
||||||
|
|
||||||
|
|
||||||
]]
|
|
||||||
send_mouse_entity =
|
|
||||||
net.create_event("send_mouse_entity", true)
|
|
||||||
:: types.NetEvent<number, Part?, number?, string?>,
|
|
||||||
|
|
||||||
--[=[
|
|
||||||
Requests the server to validate a query
|
|
||||||
world: number
|
|
||||||
query: string
|
|
||||||
]=]
|
|
||||||
validate_query =
|
|
||||||
net.create_event("validate_query")
|
|
||||||
:: types.NetEvent<number, string>,
|
|
||||||
|
|
||||||
--[=[
|
|
||||||
Result of the validation
|
|
||||||
world: number query: string, terms: {}, ok: boolean, message: string?
|
|
||||||
]=]
|
|
||||||
validate_result =
|
|
||||||
net.create_event("validate_result")
|
|
||||||
:: types.NetEvent<number, string, Query?, boolean, string?>,
|
|
||||||
|
|
||||||
--[=[
|
|
||||||
Requests a server to initiate replication of a query.
|
|
||||||
|
|
||||||
world: number
|
|
||||||
id: number
|
|
||||||
query_id: number
|
|
||||||
query: string
|
|
||||||
]=]
|
|
||||||
request_query =
|
|
||||||
net.create_event("replicate_query")
|
|
||||||
:: types.NetEvent<number, number, string>,
|
|
||||||
|
|
||||||
--[=[
|
|
||||||
Disconnects query
|
|
||||||
|
|
||||||
query_id: number
|
|
||||||
]=]
|
|
||||||
disconnect_query =
|
|
||||||
net.create_event("disconnect_query")
|
|
||||||
:: types.NetEvent<number>,
|
|
||||||
|
|
||||||
--[=[
|
|
||||||
Changes the offsets to query for
|
|
||||||
|
|
||||||
query_id: number
|
|
||||||
from: number
|
|
||||||
to: number
|
|
||||||
]=]
|
|
||||||
advance_query_page =
|
|
||||||
net.create_event("advance_query_page")
|
|
||||||
:: types.NetEvent<number, number, number>,
|
|
||||||
|
|
||||||
--- pause the query
|
|
||||||
--- query id
|
|
||||||
--- should pause
|
|
||||||
pause_query =
|
|
||||||
net.create_event("pause_query")
|
|
||||||
:: types.NetEvent<number, boolean>,
|
|
||||||
|
|
||||||
--- refreshes query results
|
|
||||||
--- query_id
|
|
||||||
refresh_results =
|
|
||||||
net.create_event("refresh_query")
|
|
||||||
:: types.NetEvent<number>,
|
|
||||||
|
|
||||||
--[=[
|
|
||||||
Updates a single result
|
|
||||||
|
|
||||||
query_id: number
|
|
||||||
frame: number
|
|
||||||
column: number
|
|
||||||
row: number
|
|
||||||
value: any
|
|
||||||
]=]
|
|
||||||
update_query_result =
|
|
||||||
net.create_event("update_query_result", true)
|
|
||||||
:: types.NetEvent<(number, number, number, number, any)>,
|
|
||||||
|
|
||||||
--[=[
|
|
||||||
Counts the total number of entities
|
|
||||||
|
|
||||||
query id: number
|
|
||||||
count: number
|
|
||||||
]=]
|
|
||||||
|
|
||||||
count_total_entities =
|
|
||||||
net.create_event("count_total_entities", true)
|
|
||||||
:: types.NetEvent<number, number>,
|
|
||||||
|
|
||||||
--[=[
|
|
||||||
Requests a server to initiate replication of a scheduler
|
|
||||||
]=]
|
|
||||||
request_scheduler =
|
|
||||||
net.create_event("initiate_replicate_scheduler") ::
|
|
||||||
types.NetEvent<number>,
|
|
||||||
|
|
||||||
--[=[
|
|
||||||
Requests the server to stop replicating a scheduler
|
|
||||||
]=]
|
|
||||||
disconnect_scheduler =
|
|
||||||
net.create_event("disconnect_replicate_scheduler") ::
|
|
||||||
types.NetEvent<number>,
|
|
||||||
|
|
||||||
--[=[
|
|
||||||
Updates any static data about systems (like new systems)
|
|
||||||
|
|
||||||
systemid: number
|
|
||||||
static_data: {}
|
|
||||||
]=]
|
|
||||||
scheduler_system_static_update =
|
|
||||||
net.create_event("scheduler_system_update_static") ::
|
|
||||||
types.NetEvent<number, number, types.SystemData?>,
|
|
||||||
|
|
||||||
--[=[
|
|
||||||
Appends a frame to a system
|
|
||||||
|
|
||||||
systemid: number
|
|
||||||
frame_count: number
|
|
||||||
time_took: number
|
|
||||||
]=]
|
|
||||||
scheduler_system_update =
|
|
||||||
net.create_event("append_frame_system", true) ::
|
|
||||||
types.NetEvent<number, number, number, number>,
|
|
||||||
|
|
||||||
--[=[
|
|
||||||
Pauses a system
|
|
||||||
|
|
||||||
scheduler: number
|
|
||||||
systemid: number
|
|
||||||
paused: boolean
|
|
||||||
]=]
|
|
||||||
scheduler_system_pause =
|
|
||||||
net.create_event("scheduler_pause") ::
|
|
||||||
types.NetEvent<number, number, boolean>,
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Validates a component
|
|
||||||
|
|
||||||
world: number
|
|
||||||
component: string
|
|
||||||
]]
|
|
||||||
validate_entity_component =
|
|
||||||
net.create_event("validate_entity_component") ::
|
|
||||||
types.NetEvent<number, string>,
|
|
||||||
|
|
||||||
--[[
|
|
||||||
entity component reslt
|
|
||||||
|
|
||||||
world: number
|
|
||||||
component: string
|
|
||||||
ok: boolean
|
|
||||||
reason: string
|
|
||||||
]]
|
|
||||||
validate_entity_component_result =
|
|
||||||
net.create_event("validate_entity_component_result") ::
|
|
||||||
types.NetEvent<number, string, boolean, string>,
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Inspect a entity's components
|
|
||||||
|
|
||||||
world: number
|
|
||||||
entity: number,
|
|
||||||
inspectid: number
|
|
||||||
]]
|
|
||||||
|
|
||||||
inspect_entity =
|
|
||||||
net.create_event("inspect_entity") ::
|
|
||||||
types.NetEvent<number, number, number>,
|
|
||||||
|
|
||||||
--[=[
|
|
||||||
Gets the component of an entity
|
|
||||||
|
|
||||||
inspect: number
|
|
||||||
component: string
|
|
||||||
]=]
|
|
||||||
get_component =
|
|
||||||
net.create_event("get_entity_component") ::
|
|
||||||
types.NetEvent<number, string>,
|
|
||||||
|
|
||||||
--[=[
|
|
||||||
Returns the component of an entity
|
|
||||||
|
|
||||||
inspect: number
|
|
||||||
component: string
|
|
||||||
value: string
|
|
||||||
]=]
|
|
||||||
return_component =
|
|
||||||
net.create_event("return_entity_component") ::
|
|
||||||
types.NetEvent<number, string, string>,
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Delete entity
|
|
||||||
|
|
||||||
inspectid: number
|
|
||||||
]]
|
|
||||||
delete_entity =
|
|
||||||
net.create_event("delete_entity") ::
|
|
||||||
types.NetEvent<number>,
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Stops inspecting a entity
|
|
||||||
|
|
||||||
inspectid: number
|
|
||||||
]]
|
|
||||||
stop_inspect_entity =
|
|
||||||
net.create_event("stop_inspect_entity") ::
|
|
||||||
types.NetEvent<number>,
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Updates a entity
|
|
||||||
|
|
||||||
inspectid: number
|
|
||||||
changes: {[component]: string}
|
|
||||||
]]
|
|
||||||
update_entity =
|
|
||||||
net.create_event("update_entity") ::
|
|
||||||
types.NetEvent<number, {[string]: string}>,
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Update the settings when dealing with inspecting
|
|
||||||
|
|
||||||
inspectid: nuimber,
|
|
||||||
settings: {}
|
|
||||||
]]
|
|
||||||
update_inspect_settings =
|
|
||||||
net.create_event("inspect_entity_settings_update") ::
|
|
||||||
types.NetEvent<number, {paused: boolean}>,
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Inspector update
|
|
||||||
|
|
||||||
inspectid: number
|
|
||||||
key: string
|
|
||||||
value: string
|
|
||||||
]]
|
|
||||||
inspect_entity_update =
|
|
||||||
net.create_event("inspect_entity_update") ::
|
|
||||||
types.NetEvent<number, string, string?>,
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Creates a watch on a system
|
|
||||||
|
|
||||||
scheduler: number,
|
|
||||||
system: number
|
|
||||||
watchid: number
|
|
||||||
]]
|
|
||||||
create_watch =
|
|
||||||
net.create_event("create_watch") ::
|
|
||||||
types.NetEvent<number, number, number>,
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Removes a watch on a system
|
|
||||||
|
|
||||||
watchid: number
|
|
||||||
]]
|
|
||||||
remove_watch =
|
|
||||||
net.create_event("remove_watch") ::
|
|
||||||
types.NetEvent<number>,
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Retrieves data about a frame for a watch
|
|
||||||
|
|
||||||
watchid: number
|
|
||||||
frame: number
|
|
||||||
]]
|
|
||||||
request_watch_data =
|
|
||||||
net.create_event("request_watch_data") ::
|
|
||||||
types.NetEvent<number, number>,
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Updates watch data for a frame
|
|
||||||
|
|
||||||
watchid: number
|
|
||||||
frame: number
|
|
||||||
changes: types.WatchLoggedChanges
|
|
||||||
]]
|
|
||||||
update_watch_data =
|
|
||||||
net.create_event("update_watch_data") ::
|
|
||||||
types.NetEvent<number, number, types.WatchLoggedChanges?>,
|
|
||||||
|
|
||||||
start_record_watch =
|
|
||||||
net.create_event("start_record_watch") ::
|
|
||||||
types.NetEvent<number>,
|
|
||||||
|
|
||||||
stop_watch =
|
|
||||||
net.create_event("stop_watch") ::
|
|
||||||
types.NetEvent<number>,
|
|
||||||
|
|
||||||
clear_watch =
|
|
||||||
net.create_event("clear_watch") ::
|
|
||||||
types.NetEvent<number>,
|
|
||||||
|
|
||||||
connect_watch =
|
|
||||||
net.create_event("connect_to_watch") ::
|
|
||||||
types.NetEvent<number>,
|
|
||||||
|
|
||||||
disconnect_watch =
|
|
||||||
net.create_event("disconnect_watch") ::
|
|
||||||
types.NetEvent<number>,
|
|
||||||
|
|
||||||
update_overview =
|
|
||||||
net.create_event("update_watch_overview", true) ::
|
|
||||||
types.NetEvent<number, number, number>
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
--!nocheck
|
|
||||||
local types = require(script.Parent.types)
|
|
||||||
|
|
||||||
local function reverse(connector: types.IncomingConnector): types.OutgoingConnector
|
|
||||||
return {
|
|
||||||
host = connector.host,
|
|
||||||
to_vm = connector.from_vm,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
return reverse
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
--[[
|
|
||||||
|
|
||||||
A rudimentary signal class. Yielding may cause bugs.
|
|
||||||
|
|
||||||
]]
|
|
||||||
|
|
||||||
local signal = {}
|
|
||||||
signal.__index = signal
|
|
||||||
|
|
||||||
type Connection = { disconnect: (any?) -> (), reconnect: (any?) -> () }
|
|
||||||
export type Signal<T... = ...unknown> = {
|
|
||||||
|
|
||||||
class_name: "Signal",
|
|
||||||
|
|
||||||
connect: (Signal<T...>, callback: (T...) -> ()) -> Connection,
|
|
||||||
wait: (Signal<T...>) -> T...,
|
|
||||||
once: (Signal<T...>, callback: (T...) -> ()) -> Connection,
|
|
||||||
|
|
||||||
callbacks: { [(T...) -> ()]: true },
|
|
||||||
}
|
|
||||||
export type SignalInternal<T... = ...unknown> = Signal<T...> & {
|
|
||||||
fire: (SignalInternal<T...>, T...) -> (),
|
|
||||||
}
|
|
||||||
|
|
||||||
function signal.connect<T...>(self: Signal<T...>, callback: (T...) -> ())
|
|
||||||
assert(type(callback) == "function")
|
|
||||||
self.callbacks[callback] = true
|
|
||||||
|
|
||||||
return {
|
|
||||||
disconnect = function() self.callbacks[callback] = nil end,
|
|
||||||
reconnect = function() self.callbacks[callback] = true end,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
function signal.fire<T...>(self: Signal<T...>, ...: T...)
|
|
||||||
for callback in self.callbacks do
|
|
||||||
callback(...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function signal.once<T...>(self: Signal<T...>, callback: (T...) -> ())
|
|
||||||
local connection
|
|
||||||
connection = self:connect(function(...)
|
|
||||||
connection:disconnect()
|
|
||||||
callback(...)
|
|
||||||
end)
|
|
||||||
|
|
||||||
return connection
|
|
||||||
end
|
|
||||||
|
|
||||||
function signal.wait<T...>(self: Signal<T...>)
|
|
||||||
local thread = coroutine.running()
|
|
||||||
|
|
||||||
local connection = self:connect(function(...) coroutine.resume(thread, ...) end)
|
|
||||||
local packed = { coroutine.yield() }
|
|
||||||
connection:disconnect()
|
|
||||||
return unpack(packed)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function new_signal<T...>(): (Signal<T...>, (T...) -> ())
|
|
||||||
local self = setmetatable({
|
|
||||||
class_name = "Signal",
|
|
||||||
callbacks = {},
|
|
||||||
}, signal)
|
|
||||||
|
|
||||||
local function fire(...)
|
|
||||||
for callback in self.callbacks :: any do
|
|
||||||
callback(...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return self :: any, fire
|
|
||||||
end
|
|
||||||
|
|
||||||
return new_signal
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
local Players = game:GetService("Players")
|
|
||||||
--[[
|
|
||||||
|
|
||||||
a utility library to handle checking traffic and determining if the sender is
|
|
||||||
permitted to send the given data.
|
|
||||||
|
|
||||||
]]
|
|
||||||
|
|
||||||
local signal = require(script.Parent.signal)
|
|
||||||
|
|
||||||
local traffic_check = {}
|
|
||||||
|
|
||||||
local whitelist_player_to = {}
|
|
||||||
|
|
||||||
local on_fail, fire = signal()
|
|
||||||
|
|
||||||
--- A function that needs to be overwritten by the user.
|
|
||||||
--- This function is used to find out what permissions a user may have.
|
|
||||||
traffic_check.can_use_jabby = function(player: Player)
|
|
||||||
local is_studio = game:GetService("RunService"):IsStudio()
|
|
||||||
|
|
||||||
return is_studio --is_owner or is_studio
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Runs a callback defined by the developer to determine if a player is allowed
|
|
||||||
--- to use a given function
|
|
||||||
local function communication_is_allowed(from: "server" | Player, to: "server" | Player, dont_whitelist: boolean?)
|
|
||||||
if from == "server" then return true end
|
|
||||||
|
|
||||||
whitelist_player_to[from] = whitelist_player_to[from] or {}
|
|
||||||
whitelist_player_to[to] = whitelist_player_to[to] or {}
|
|
||||||
|
|
||||||
if traffic_check.can_use_jabby(from) or whitelist_player_to[from][to] then
|
|
||||||
if dont_whitelist then return true end
|
|
||||||
whitelist_player_to[to][from] = from
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Runs the given check and fires the on_fail signal if the player fails the
|
|
||||||
--- check.
|
|
||||||
local function check(from: "server" | Player, to: "server" | Player, dont_whitelist: boolean?)
|
|
||||||
if communication_is_allowed(from, to, dont_whitelist) then
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
fire(from)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function check_no_wl(from: "server" | Player)
|
|
||||||
if from == "server" then return true end
|
|
||||||
if traffic_check.can_use_jabby(from) then
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
-- print(from, "cant use jabby")
|
|
||||||
fire(from)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function _whitelist(from: "server" | Player, to: "server" | Player)
|
|
||||||
whitelist_player_to[from] = whitelist_player_to[from] or {}
|
|
||||||
whitelist_player_to[to] = whitelist_player_to[to] or {}
|
|
||||||
whitelist_player_to[from][to] = from
|
|
||||||
end
|
|
||||||
|
|
||||||
traffic_check.communication_is_allowed = communication_is_allowed
|
|
||||||
traffic_check.check_no_wl = check_no_wl
|
|
||||||
traffic_check.check = check
|
|
||||||
|
|
||||||
traffic_check._whitelist = _whitelist
|
|
||||||
|
|
||||||
traffic_check.on_fail = on_fail
|
|
||||||
|
|
||||||
Players.PlayerRemoving:Connect(function(player)
|
|
||||||
whitelist_player_to[player] = nil
|
|
||||||
end)
|
|
||||||
|
|
||||||
return traffic_check
|
|
||||||
|
|
@ -1,117 +0,0 @@
|
||||||
local jecs = require(script.Parent.Parent.jecs)
|
|
||||||
type host = "client" | "server"
|
|
||||||
|
|
||||||
export type IncomingConnector = {
|
|
||||||
host: Player | "server",
|
|
||||||
from_vm: number,
|
|
||||||
to_vm: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type OutgoingConnector = {
|
|
||||||
host: Player | "server",
|
|
||||||
to_vm: number?, -- not specifying a vm makes it received by all
|
|
||||||
from_vm: nil
|
|
||||||
}
|
|
||||||
|
|
||||||
export type NetEvent<T...> = {
|
|
||||||
|
|
||||||
type: "event",
|
|
||||||
|
|
||||||
fire: (any, connector: OutgoingConnector, T...) -> (),
|
|
||||||
connect: (any, callback: (connector: IncomingConnector, T...) -> ()) -> RBXScriptConnection,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type NetCallback<T..., U...> = {
|
|
||||||
|
|
||||||
type: "callback",
|
|
||||||
|
|
||||||
invoke: (any, connector: OutgoingConnector, T...) -> U...,
|
|
||||||
set_callback: (any, callback: (connector: IncomingConnector, T...) -> U...) -> (),
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SystemId = number
|
|
||||||
|
|
||||||
export type SystemTag = "processing" | "finished" | "paused"
|
|
||||||
export type SystemSettingData = {
|
|
||||||
name: string?,
|
|
||||||
phase: string?,
|
|
||||||
layout_order: number?,
|
|
||||||
paused: boolean?
|
|
||||||
}
|
|
||||||
export type SystemData = {
|
|
||||||
name: string,
|
|
||||||
phase: string?,
|
|
||||||
layout_order: number,
|
|
||||||
paused: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
type ChangeTypes = "remove" | "clear" | "delete" | "add" | "set" | "entity" | "component"
|
|
||||||
export type WatchLoggedChanges = {
|
|
||||||
types: {ChangeTypes},
|
|
||||||
entities: {jecs.Entity<any>},
|
|
||||||
component: {jecs.Entity<any>},
|
|
||||||
values: {string},
|
|
||||||
worlds: {jecs.World}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SystemWatch = {
|
|
||||||
--- enables Lua Object Notation.
|
|
||||||
--- incurs a significant performance penalty.
|
|
||||||
enable_lon: boolean,
|
|
||||||
--- the current frame to process
|
|
||||||
frame: number,
|
|
||||||
|
|
||||||
frames: {[number]: WatchLoggedChanges}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SystemLabel = {}
|
|
||||||
|
|
||||||
export type SystemFrame = {
|
|
||||||
i: number,
|
|
||||||
s: number
|
|
||||||
}
|
|
||||||
|
|
||||||
type WatchData = {active: boolean, watch: SystemWatch, untrack: () -> ()}
|
|
||||||
|
|
||||||
export type Scheduler = {
|
|
||||||
class_name: "Scheduler",
|
|
||||||
name: string,
|
|
||||||
|
|
||||||
valid_system_ids: {[SystemId]: true},
|
|
||||||
system_data: {[SystemId]: SystemData},
|
|
||||||
system_data_updated: {[SystemId]: true},
|
|
||||||
system_frames: {[SystemId]: SystemFrame},
|
|
||||||
system_frames_updated: {[SystemId]: {[SystemFrame]: true}},
|
|
||||||
system_watches: {[SystemId]: {WatchData}},
|
|
||||||
|
|
||||||
register_system: (Scheduler, settings: SystemSettingData?) -> SystemId,
|
|
||||||
set_system_data: (Scheduler, system: SystemId, settings: SystemSettingData) -> (),
|
|
||||||
get_system_data: (Scheduler, system: SystemId) -> SystemSettingData,
|
|
||||||
create_watch_for_system: (Scheduler, system: SystemId) -> WatchData,
|
|
||||||
remove_system: (Scheduler, system: SystemId) -> (),
|
|
||||||
|
|
||||||
-- mark_system_frame_start: (Scheduler, system: SystemId) -> (),
|
|
||||||
-- mark_system_frame_end: (Scheduler, system: SystemId, s: number?) -> (),
|
|
||||||
-- append_extra_frame_data: (Scheduler, system: SystemId, label: SystemLabel) -> (),
|
|
||||||
--- this should call mark_system_frame_start and mark_system_frame_end for you
|
|
||||||
run: <T...>(Scheduler, system: SystemId, system: () -> (), T...) -> (),
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export type World = {
|
|
||||||
class_name: "World",
|
|
||||||
name: string,
|
|
||||||
world: jecs.World,
|
|
||||||
|
|
||||||
entities: {[Instance]: jecs.Entity<any>}?,
|
|
||||||
get_entity_from_part: ((part: BasePart) -> (jecs.Entity<any>?, Part?))?
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Application<T> = {
|
|
||||||
class_name: "app",
|
|
||||||
name: string,
|
|
||||||
|
|
||||||
mount: (props: T, destroy: () -> ()) -> Instance
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
-- videx/store.luau
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
local vide = require(script.Parent.Parent.vide)
|
|
||||||
local source = vide.source
|
|
||||||
|
|
||||||
local NULL = newproxy()
|
|
||||||
|
|
||||||
local Store = {}
|
|
||||||
|
|
||||||
--[=[
|
|
||||||
Creates a new store object that receives some initial state and then returns
|
|
||||||
a table with the same structure, but all keys of the given table will be reactive.
|
|
||||||
|
|
||||||
When accessed inside a reactive scope, the reactive scope will update whenever
|
|
||||||
the key that is accessed is changed.
|
|
||||||
|
|
||||||
@param initial_state `T : {[string]: any}` The initial state the store will start in.
|
|
||||||
@param mutations `() -> {[string]: (T, ...any) -> ...any}?` A list of functions that mutate the data.
|
|
||||||
@return `T & U` A resulting table that
|
|
||||||
]=]
|
|
||||||
function Store.new<T, U>(
|
|
||||||
initial_state: T & {},
|
|
||||||
mutations: (T & U) -> U
|
|
||||||
): T & U
|
|
||||||
local sources = {}
|
|
||||||
|
|
||||||
for i, v in initial_state :: any do
|
|
||||||
local src = source(v ~= NULL and v or nil)
|
|
||||||
sources[i] = src
|
|
||||||
end
|
|
||||||
|
|
||||||
local internal_proxy = {}
|
|
||||||
|
|
||||||
setmetatable(internal_proxy, {
|
|
||||||
__index = function(_, index)
|
|
||||||
return sources[index]()
|
|
||||||
end,
|
|
||||||
__newindex = function(_, index, value)
|
|
||||||
sources[index](value)
|
|
||||||
end
|
|
||||||
})
|
|
||||||
|
|
||||||
local external_proxy = {}
|
|
||||||
|
|
||||||
setmetatable(external_proxy :: any, {
|
|
||||||
__index = function(_, index)
|
|
||||||
local src = sources[index]
|
|
||||||
if src == nil then error(`invalid index {index}`, 2) end
|
|
||||||
return src()
|
|
||||||
end,
|
|
||||||
|
|
||||||
__newindex = function(_, index, value)
|
|
||||||
sources[index](value)
|
|
||||||
end
|
|
||||||
})
|
|
||||||
|
|
||||||
for i, v in next, mutations(internal_proxy :: any) :: any do
|
|
||||||
if rawget(external_proxy, i) then
|
|
||||||
error(`duplicate field "{i}"`, 2)
|
|
||||||
end
|
|
||||||
rawset(external_proxy, i, v)
|
|
||||||
end
|
|
||||||
|
|
||||||
return external_proxy :: T & U & {}
|
|
||||||
end
|
|
||||||
|
|
||||||
--- A special symbol used to indicate that a value should be nil within a Store.
|
|
||||||
Store.null = NULL :: nil
|
|
||||||
|
|
||||||
return Store
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
--[[
|
|
||||||
|
|
||||||
Provides a unique identifier for a VM.
|
|
||||||
|
|
||||||
This currently cannot be tested unless there is some parallel system for jest.
|
|
||||||
|
|
||||||
]]
|
|
||||||
|
|
||||||
local SharedTableRegistry = game:GetService("SharedTableRegistry")
|
|
||||||
|
|
||||||
local shared_table = SharedTableRegistry:GetSharedTable("_gorp_common_vm_count")
|
|
||||||
shared_table.id = shared_table.id or 0
|
|
||||||
|
|
||||||
return SharedTable.increment(shared_table, "id", 1)
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
local Players = game:GetService("Players")
|
|
||||||
local RunService = game:GetService("RunService")
|
|
||||||
|
|
||||||
local loop = require(script.Parent.modules.loop)
|
|
||||||
local remotes = require(script.Parent.modules.remotes)
|
|
||||||
local traffic_check = require(script.Parent.modules.traffic_check)
|
|
||||||
local vm_id = require(script.Parent.modules.vm_id)
|
|
||||||
local function broadcast()
|
|
||||||
for _, player in Players:GetPlayers() do
|
|
||||||
if not traffic_check.can_use_jabby(player) then continue end
|
|
||||||
remotes.new_server_registered:fire({
|
|
||||||
host = player,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
task.delay(0, broadcast)
|
|
||||||
|
|
||||||
local systems = script.systems
|
|
||||||
local loop = loop (
|
|
||||||
`jabby-host:{
|
|
||||||
if RunService:IsServer() then "server" else "client"
|
|
||||||
}-vm:{vm_id}`,
|
|
||||||
nil,
|
|
||||||
{i = 1},
|
|
||||||
|
|
||||||
systems.ping,
|
|
||||||
systems.replicate_core,
|
|
||||||
systems.replicate_scheduler,
|
|
||||||
systems.replicate_registry,
|
|
||||||
systems.replicate_system_watch,
|
|
||||||
systems.mouse_pointer,
|
|
||||||
systems.entity
|
|
||||||
)
|
|
||||||
|
|
||||||
RunService.PostSimulation:Connect(loop)
|
|
||||||
|
|
||||||
return {
|
|
||||||
|
|
||||||
broadcast = broadcast
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
--[[
|
|
||||||
|
|
||||||
anything in here is considered "public" and will be visible to jabby clients
|
|
||||||
|
|
||||||
]]
|
|
||||||
|
|
||||||
local self = {
|
|
||||||
|
|
||||||
updated = false,
|
|
||||||
|
|
||||||
} :: {
|
|
||||||
updated: boolean,
|
|
||||||
[number]: any
|
|
||||||
}
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
@ -1,402 +0,0 @@
|
||||||
--!strict
|
|
||||||
|
|
||||||
local escape_chars = {
|
|
||||||
a = "\a",
|
|
||||||
b = "\b",
|
|
||||||
f = "\f",
|
|
||||||
n = "\n",
|
|
||||||
r = "\r",
|
|
||||||
t = "\t",
|
|
||||||
v = "\v",
|
|
||||||
["\\"] = "\\",
|
|
||||||
["\""] = "\"",
|
|
||||||
["\'"] = "\'"
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Position = {
|
|
||||||
line: number,
|
|
||||||
pos: number,
|
|
||||||
col: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type String = {type: "string", s: string}
|
|
||||||
export type Number = {type: "number", s: number}
|
|
||||||
export type Identifier = {type: "identifier", s: string}
|
|
||||||
export type Operator = {type: "operator", s: "#" | "!" | "*" | "$"}
|
|
||||||
export type Symbol = {type: "symbol", s: "(" | ")" | ";" | ","}
|
|
||||||
export type EOF = {type: "eof", s: "eof"}
|
|
||||||
|
|
||||||
export type Token =
|
|
||||||
String
|
|
||||||
| Number
|
|
||||||
| Identifier
|
|
||||||
| Symbol
|
|
||||||
| Operator
|
|
||||||
| EOF
|
|
||||||
|
|
||||||
|
|
||||||
export type Stream = {
|
|
||||||
next: () -> Token,
|
|
||||||
peek: (n: number?) -> Token,
|
|
||||||
eof: () -> boolean,
|
|
||||||
croak: (msg: string) -> (),
|
|
||||||
pos: () -> Position
|
|
||||||
}
|
|
||||||
|
|
||||||
local function stream(input: string)
|
|
||||||
|
|
||||||
local pos = 0
|
|
||||||
local line = 1
|
|
||||||
local col = 1
|
|
||||||
|
|
||||||
local function peek(): string
|
|
||||||
return string.sub(input, pos+1, pos+1)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function next(): string
|
|
||||||
local char = peek()
|
|
||||||
pos += 1
|
|
||||||
|
|
||||||
if char == "\n" then line += 1; col = 1
|
|
||||||
else col += 1 end
|
|
||||||
|
|
||||||
return char
|
|
||||||
end
|
|
||||||
|
|
||||||
local function eof(): boolean
|
|
||||||
return peek() == ""
|
|
||||||
end
|
|
||||||
|
|
||||||
local function position()
|
|
||||||
return {
|
|
||||||
pos = pos,
|
|
||||||
line = line,
|
|
||||||
col = col
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local function croak(msg)
|
|
||||||
error(`{msg} ({line}:{col})`, 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
peek = peek,
|
|
||||||
next = next,
|
|
||||||
eof = eof,
|
|
||||||
croak = croak,
|
|
||||||
pos = position
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
local function lex(source: string): Stream
|
|
||||||
|
|
||||||
local input = stream(source)
|
|
||||||
|
|
||||||
local function is_whitespace(char: string)
|
|
||||||
return not not string.match(char, "[\t ]")
|
|
||||||
end
|
|
||||||
|
|
||||||
local function is_digit(char: string)
|
|
||||||
return not not (string.match(char, "%d"))
|
|
||||||
end
|
|
||||||
|
|
||||||
local function is_start_identifier(char: string)
|
|
||||||
return not not string.match(char, "[%a_]")
|
|
||||||
end
|
|
||||||
|
|
||||||
local function is_identifier(char: string)
|
|
||||||
return not not string.match(char, "[%a_:%.]")
|
|
||||||
end
|
|
||||||
|
|
||||||
local function is_op_char(char: string)
|
|
||||||
return char == "#" or char == "!" or char == "*" or char == "$"
|
|
||||||
end
|
|
||||||
|
|
||||||
local function is_punc(char: string)
|
|
||||||
return not not string.match(char, "[%(%);,]")
|
|
||||||
end
|
|
||||||
|
|
||||||
local function read_while(predicate: (char: string) -> boolean)
|
|
||||||
local str = ""
|
|
||||||
while input.eof() == false and predicate(input.peek()) do
|
|
||||||
str ..= input.next()
|
|
||||||
end
|
|
||||||
return str
|
|
||||||
end
|
|
||||||
|
|
||||||
local function skip_whitespace()
|
|
||||||
read_while(is_whitespace)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function read_string(): String
|
|
||||||
local escaped = false
|
|
||||||
local token = ""
|
|
||||||
local eliminator = input.next()
|
|
||||||
local from = input.pos()
|
|
||||||
|
|
||||||
while input.eof() == false and (input.peek() ~= eliminator or escaped) do
|
|
||||||
local char = input.next()
|
|
||||||
|
|
||||||
if char == "\\" then
|
|
||||||
escaped = true
|
|
||||||
end
|
|
||||||
|
|
||||||
if escaped then
|
|
||||||
token ..= escape_chars[char] or input.croak(`cannot escape {char}`)
|
|
||||||
escaped = false
|
|
||||||
else
|
|
||||||
token ..= char
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local to = input.pos()
|
|
||||||
|
|
||||||
-- print("t", token, eliminator, input.peek())
|
|
||||||
|
|
||||||
if input.peek() ~= eliminator then input.croak("unterminated string") end
|
|
||||||
input.next()
|
|
||||||
return {type = "string", s = token, from = from, to = to}
|
|
||||||
end
|
|
||||||
|
|
||||||
local function read_number(): Number
|
|
||||||
local decimal_pointer = false
|
|
||||||
local from = input.pos()
|
|
||||||
local token = read_while(function(char)
|
|
||||||
if decimal_pointer and char == "." then return false end
|
|
||||||
if char == "." then decimal_pointer = true end
|
|
||||||
return is_digit(char)
|
|
||||||
end)
|
|
||||||
local to = input.pos()
|
|
||||||
|
|
||||||
local n = tonumber(token)
|
|
||||||
|
|
||||||
if not n then
|
|
||||||
input.croak(`could not read {token} as number`)
|
|
||||||
end
|
|
||||||
|
|
||||||
return {type = "number", s = assert(n), from = from, to = to}
|
|
||||||
end
|
|
||||||
|
|
||||||
local function read_identifier(): Identifier
|
|
||||||
local from = input.pos()
|
|
||||||
local token = read_while(is_identifier)
|
|
||||||
local to = input.pos()
|
|
||||||
|
|
||||||
-- if table.find(keywords, token) then
|
|
||||||
-- return {type = "keyword", s = token :: any, from = from, to = to}
|
|
||||||
-- else
|
|
||||||
return {type = "identifier", s = token, from = from, to = to}
|
|
||||||
-- end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function read_next(): Token
|
|
||||||
skip_whitespace()
|
|
||||||
if input.eof() then return {type = "eof", s = "eof"} end
|
|
||||||
|
|
||||||
local char = input.peek()
|
|
||||||
|
|
||||||
if char == "\"" or char == "'" then return read_string() end
|
|
||||||
if is_digit(char) then return read_number() end
|
|
||||||
if is_start_identifier(char) then return read_identifier() end
|
|
||||||
if is_op_char(char) then return {type = "operator", s = input.next() :: any} end
|
|
||||||
if is_punc(char) then return {type = "symbol", s = input.next() :: any} end
|
|
||||||
|
|
||||||
input.croak(`cannot lex {char}`)
|
|
||||||
error("fail")
|
|
||||||
end
|
|
||||||
|
|
||||||
local current: {Token} = {}
|
|
||||||
|
|
||||||
local function next()
|
|
||||||
local token = table.remove(current, 1)
|
|
||||||
return if token == nil then read_next() else token
|
|
||||||
end
|
|
||||||
|
|
||||||
local function peek(n: number?)
|
|
||||||
local n = n or 1
|
|
||||||
while #current < n do
|
|
||||||
table.insert(current, read_next())
|
|
||||||
end
|
|
||||||
return current[n]
|
|
||||||
end
|
|
||||||
|
|
||||||
local function eof()
|
|
||||||
return peek().type == "eof"
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
|
|
||||||
peek = peek,
|
|
||||||
next = next,
|
|
||||||
eof = eof,
|
|
||||||
croak = input.croak,
|
|
||||||
pos = input.pos
|
|
||||||
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
type Wildcard = {
|
|
||||||
type: "Wildcard",
|
|
||||||
name: "*"
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Value = {
|
|
||||||
type: "Name",
|
|
||||||
name: string
|
|
||||||
} | {
|
|
||||||
type: "Entity",
|
|
||||||
entity: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PureComponent = {
|
|
||||||
type: "Component",
|
|
||||||
query: boolean,
|
|
||||||
exclude: boolean,
|
|
||||||
value: Value,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Relationship = {
|
|
||||||
type: "Relationship",
|
|
||||||
query: boolean,
|
|
||||||
exclude: boolean,
|
|
||||||
left: PureComponent | Wildcard,
|
|
||||||
right: PureComponent | Wildcard
|
|
||||||
}
|
|
||||||
|
|
||||||
type Component = Relationship | PureComponent | Wildcard
|
|
||||||
|
|
||||||
local function parse(input: string): {PureComponent | Relationship}
|
|
||||||
local lexer = lex(input)
|
|
||||||
|
|
||||||
local result: {PureComponent | Relationship} = {}
|
|
||||||
|
|
||||||
local should_query = true
|
|
||||||
local should_exclude = false
|
|
||||||
local should_relationship = false
|
|
||||||
local interpret_pointer = false
|
|
||||||
local components: {Component} = {}
|
|
||||||
|
|
||||||
while true do
|
|
||||||
local symbol = lexer.peek()
|
|
||||||
-- print2(symbol)
|
|
||||||
if symbol.type == "eof" then
|
|
||||||
break
|
|
||||||
elseif interpret_pointer or symbol.type == "number" then
|
|
||||||
if not interpret_pointer then
|
|
||||||
lexer.croak("expected $")
|
|
||||||
elseif symbol.type ~= "number" then
|
|
||||||
lexer.croak("expected number")
|
|
||||||
error("")
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(components, {
|
|
||||||
type = "Component",
|
|
||||||
query = should_query,
|
|
||||||
exclude = should_exclude,
|
|
||||||
value = {type = "Entity", entity = tonumber(lexer.next().s) :: number}
|
|
||||||
})
|
|
||||||
|
|
||||||
should_query = if should_relationship then should_query else true
|
|
||||||
should_exclude = if should_relationship then should_exclude else false
|
|
||||||
interpret_pointer = false
|
|
||||||
if lexer.peek().type ~= "symbol" and lexer.peek().type ~= "eof" then lexer.croak("expected symbol or eof after identifier") end
|
|
||||||
elseif symbol.type == "operator" then
|
|
||||||
if symbol.s == "#" then
|
|
||||||
if should_relationship then lexer.croak("cannot tag inside relationship") end
|
|
||||||
should_query = false
|
|
||||||
lexer.next()
|
|
||||||
elseif symbol.s == "!" then
|
|
||||||
if should_relationship then lexer.croak("cannot exclude in relationship") end
|
|
||||||
should_exclude = true
|
|
||||||
should_query = false
|
|
||||||
lexer.next()
|
|
||||||
elseif symbol.s == "$" then
|
|
||||||
interpret_pointer = true
|
|
||||||
lexer.next()
|
|
||||||
elseif symbol.s == "*" then
|
|
||||||
if not should_relationship then lexer.croak("cannot use wildcards outside relationship") end
|
|
||||||
table.insert(components, {
|
|
||||||
type = "Wildcard",
|
|
||||||
name = "*"
|
|
||||||
})
|
|
||||||
lexer.next()
|
|
||||||
end
|
|
||||||
elseif symbol.type == "symbol" then
|
|
||||||
if symbol.s == "(" then
|
|
||||||
if should_relationship == true then lexer.croak("relationship within relationship") end
|
|
||||||
should_relationship = true
|
|
||||||
lexer.next()
|
|
||||||
elseif symbol.s == ")" then
|
|
||||||
if should_relationship == false then lexer.croak("missing (") end
|
|
||||||
if #components == 2 then
|
|
||||||
local right = table.remove(components) :: Component
|
|
||||||
local left = table.remove(components) :: Component
|
|
||||||
|
|
||||||
if left.type == "Wildcard" and right.type == "Wildcard" then
|
|
||||||
lexer.croak("both components are wildcards")
|
|
||||||
end
|
|
||||||
|
|
||||||
components = {{
|
|
||||||
type = "Relationship",
|
|
||||||
query = should_query,
|
|
||||||
exclude = should_exclude,
|
|
||||||
left = left :: any,
|
|
||||||
right = right :: any
|
|
||||||
}}
|
|
||||||
|
|
||||||
should_query = true
|
|
||||||
should_exclude = false
|
|
||||||
should_relationship = false
|
|
||||||
lexer.next()
|
|
||||||
else
|
|
||||||
lexer.croak(`expected 2 components, got {#components}`)
|
|
||||||
end
|
|
||||||
elseif symbol.s == "," or symbol.s == ";" then
|
|
||||||
if should_relationship then
|
|
||||||
lexer.next()
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
|
|
||||||
local ctype = table.remove(components)
|
|
||||||
if ctype == nil then
|
|
||||||
lexer.croak("no component provided")
|
|
||||||
error("")
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(result, ctype :: any)
|
|
||||||
|
|
||||||
should_query = true
|
|
||||||
should_exclude = false
|
|
||||||
lexer.next()
|
|
||||||
end
|
|
||||||
elseif symbol.type == "identifier" then
|
|
||||||
table.insert(components, {
|
|
||||||
type = "Component",
|
|
||||||
query = should_query,
|
|
||||||
exclude = should_exclude,
|
|
||||||
value = {type = "Name", name = lexer.next().s :: string}
|
|
||||||
})
|
|
||||||
|
|
||||||
should_query = if should_relationship then should_query else true
|
|
||||||
should_exclude = if should_relationship then should_exclude else false
|
|
||||||
if lexer.peek().type ~= "symbol" and lexer.peek().type ~= "eof" then lexer.croak("expected symbol or eof after identifier") end
|
|
||||||
elseif symbol.type == "string" then
|
|
||||||
table.insert(components, {
|
|
||||||
type = "Component",
|
|
||||||
query = should_query,
|
|
||||||
exclude = should_exclude,
|
|
||||||
value = {type = "Name", name = lexer.next().s :: string}
|
|
||||||
})
|
|
||||||
should_query = if should_relationship then should_query else true
|
|
||||||
should_exclude = if should_relationship then should_exclude else false
|
|
||||||
if lexer.peek().type ~= "symbol" and lexer.peek().type ~= "eof" then lexer.croak("expected symbol or eof after string") end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(result, table.remove(components) :: any)
|
|
||||||
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
return parse
|
|
||||||
|
|
@ -1,197 +0,0 @@
|
||||||
local types = require(script.Parent.Parent.modules.types)
|
|
||||||
local watch = require(script.Parent.watch)
|
|
||||||
|
|
||||||
type SystemId = types.SystemId
|
|
||||||
type SystemSettingData = types.SystemSettingData
|
|
||||||
type SystemTag = types.SystemTag
|
|
||||||
|
|
||||||
type SystemData = types.SystemData
|
|
||||||
type ProcessingFrame = {
|
|
||||||
started_at: number
|
|
||||||
}
|
|
||||||
type SystemFrame = types.SystemFrame
|
|
||||||
|
|
||||||
local MAX_BUFFER_SIZE = 50
|
|
||||||
|
|
||||||
local n = 0
|
|
||||||
local schedulers = {}
|
|
||||||
|
|
||||||
local function unit() end
|
|
||||||
|
|
||||||
local function create_scheduler()
|
|
||||||
|
|
||||||
local count = 1
|
|
||||||
local frames = 0
|
|
||||||
|
|
||||||
local scheduler = {
|
|
||||||
class_name = "Scheduler",
|
|
||||||
name = "Scheduler",
|
|
||||||
|
|
||||||
--- contains a map of valid system ids
|
|
||||||
valid_system_ids = {} :: {[SystemId]: true},
|
|
||||||
--- contains a list of static system data that is updated infrequently
|
|
||||||
system_data = {} :: {[SystemId]: SystemData},
|
|
||||||
--- list of system data that has updated
|
|
||||||
system_data_updated = {} :: {[SystemId]: true},
|
|
||||||
--- contains a buffer of the last couple frames of system data that is
|
|
||||||
--- refreshed constantly
|
|
||||||
system_frames = {} :: {[SystemId]: {SystemFrame}},
|
|
||||||
--- stores the frames that have been updated
|
|
||||||
system_frames_updated = {} :: {[SystemId]: {[SystemFrame]: true}},
|
|
||||||
--- contains the current frame that a system is processing
|
|
||||||
processing_frame = {} :: {[SystemId]: ProcessingFrame},
|
|
||||||
--- contains a list of watches for each system
|
|
||||||
system_watches = {} :: {[SystemId]: {{active: boolean, watch: types.SystemWatch}}}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
local function ENABLE_WATCHES(id: SystemId)
|
|
||||||
local watches = scheduler.system_watches[id]
|
|
||||||
local cleanup = {}
|
|
||||||
|
|
||||||
for i, system_watch in watches do
|
|
||||||
if system_watch.active == false then continue end
|
|
||||||
watch.step_watch(system_watch.watch)
|
|
||||||
cleanup[i] = watch.track_watch(system_watch.watch)
|
|
||||||
end
|
|
||||||
|
|
||||||
return function()
|
|
||||||
for _, stop in cleanup do
|
|
||||||
stop()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function ASSERT_SYSTEM_VALID(id: SystemId)
|
|
||||||
assert(scheduler.valid_system_ids[id], `attempt to use unknown system with id #{id}`)
|
|
||||||
end
|
|
||||||
|
|
||||||
function scheduler:register_system(settings: types.SystemSettingData?)
|
|
||||||
local id = count; count += 1
|
|
||||||
scheduler.valid_system_ids[id] = true
|
|
||||||
scheduler.system_data[id] = {
|
|
||||||
name = "UNNAMED",
|
|
||||||
phase = nil,
|
|
||||||
layout_order = 0,
|
|
||||||
paused = false
|
|
||||||
}
|
|
||||||
scheduler.system_frames[id] = {}
|
|
||||||
scheduler.system_frames_updated[id] = {}
|
|
||||||
|
|
||||||
if settings then
|
|
||||||
scheduler:set_system_data(id, settings)
|
|
||||||
end
|
|
||||||
|
|
||||||
return id
|
|
||||||
end
|
|
||||||
|
|
||||||
function scheduler:set_system_data(id: SystemId, settings: types.SystemSettingData)
|
|
||||||
ASSERT_SYSTEM_VALID(id)
|
|
||||||
|
|
||||||
for key, value in settings do
|
|
||||||
scheduler.system_data[id][key] = value
|
|
||||||
end
|
|
||||||
scheduler.system_data_updated[id] = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function scheduler:get_system_data(id: SystemId)
|
|
||||||
ASSERT_SYSTEM_VALID(id)
|
|
||||||
return scheduler.system_data[id]
|
|
||||||
end
|
|
||||||
|
|
||||||
function scheduler:remove_system(id: SystemId)
|
|
||||||
scheduler.valid_system_ids[id] = nil
|
|
||||||
scheduler.system_data[id] = nil
|
|
||||||
scheduler.system_frames[id] = nil
|
|
||||||
scheduler.system_frames_updated[id] = nil
|
|
||||||
scheduler.system_data_updated[id] = true
|
|
||||||
scheduler.system_watches[id] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function scheduler:_mark_system_frame_start(id: SystemId)
|
|
||||||
ASSERT_SYSTEM_VALID(id)
|
|
||||||
|
|
||||||
scheduler.processing_frame[id] = {
|
|
||||||
started_at = os.clock()
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
function scheduler:_mark_system_frame_end(id: SystemId, s: number?)
|
|
||||||
ASSERT_SYSTEM_VALID(id)
|
|
||||||
local now = os.clock()
|
|
||||||
local pending_frame_data = scheduler.processing_frame[id]
|
|
||||||
assert(pending_frame_data ~= nil, "no processing frame")
|
|
||||||
local frame = {
|
|
||||||
i = frames,
|
|
||||||
s = now - pending_frame_data.started_at
|
|
||||||
}
|
|
||||||
|
|
||||||
frames += 1
|
|
||||||
|
|
||||||
scheduler.processing_frame[id] = nil
|
|
||||||
scheduler.system_frames_updated[id][frame] = true
|
|
||||||
local last_frame = scheduler.system_frames[id][MAX_BUFFER_SIZE]
|
|
||||||
if last_frame then
|
|
||||||
scheduler.system_frames_updated[id][last_frame] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(scheduler.system_frames[id], 1, frame)
|
|
||||||
table.remove(scheduler.system_frames[id], MAX_BUFFER_SIZE + 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
function scheduler:append_extra_frame_data(id: SystemId, label: {})
|
|
||||||
--todo
|
|
||||||
error("todo")
|
|
||||||
end
|
|
||||||
|
|
||||||
function scheduler:run<T...>(id: SystemId, system: (T...) -> (), ...: T...)
|
|
||||||
ASSERT_SYSTEM_VALID(id)
|
|
||||||
local system_data = scheduler.system_data[id]
|
|
||||||
|
|
||||||
if system_data.paused then return end
|
|
||||||
|
|
||||||
local watches = scheduler.system_watches[id]
|
|
||||||
local cleanup_watches = unit
|
|
||||||
|
|
||||||
if watches then
|
|
||||||
cleanup_watches = ENABLE_WATCHES(id)
|
|
||||||
end
|
|
||||||
|
|
||||||
scheduler:_mark_system_frame_start(id)
|
|
||||||
system(...)
|
|
||||||
scheduler:_mark_system_frame_end(id)
|
|
||||||
|
|
||||||
cleanup_watches()
|
|
||||||
end
|
|
||||||
|
|
||||||
function scheduler:create_watch_for_system(id: SystemId)
|
|
||||||
ASSERT_SYSTEM_VALID(id)
|
|
||||||
|
|
||||||
local new_watch = watch.create_watch()
|
|
||||||
local watch_data
|
|
||||||
scheduler.system_watches[id] = scheduler.system_watches[id] or {} :: never
|
|
||||||
|
|
||||||
local function untrack()
|
|
||||||
local idx = table.find(scheduler.system_watches[id], watch_data)
|
|
||||||
table.remove(scheduler.system_watches[id], idx)
|
|
||||||
end
|
|
||||||
|
|
||||||
watch_data = {active = false, watch = new_watch, untrack = untrack}
|
|
||||||
table.insert(scheduler.system_watches[id], watch_data)
|
|
||||||
|
|
||||||
return watch_data
|
|
||||||
end
|
|
||||||
|
|
||||||
schedulers[n + 1] = scheduler
|
|
||||||
n = n + 1
|
|
||||||
|
|
||||||
return scheduler
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
|
|
||||||
create = create_scheduler,
|
|
||||||
schedulers = schedulers
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,294 +0,0 @@
|
||||||
local jecs = require(script.Parent.Parent.Parent.jecs)
|
|
||||||
local lon = require(script.Parent.Parent.Parent.modules.lon)
|
|
||||||
local queue = require(script.Parent.Parent.Parent.modules.queue)
|
|
||||||
local remotes = require(script.Parent.Parent.Parent.modules.remotes)
|
|
||||||
local reverse_connector = require(script.Parent.Parent.Parent.modules.reverse_connector)
|
|
||||||
local traffic_check = require(script.Parent.Parent.Parent.modules.traffic_check)
|
|
||||||
local types = require(script.Parent.Parent.Parent.modules.types)
|
|
||||||
local public = require(script.Parent.Parent.public)
|
|
||||||
local query_parser = require(script.Parent.Parent.query_parser)
|
|
||||||
|
|
||||||
local entity_index_try_get = jecs.entity_index_try_get
|
|
||||||
local IS_PAIR = jecs.IS_PAIR
|
|
||||||
local pair = jecs.pair
|
|
||||||
local pair_first = jecs.pair_first
|
|
||||||
local pair_second = jecs.pair_second
|
|
||||||
local empty_table = {}
|
|
||||||
|
|
||||||
local function get_all_components(world, entity): {}
|
|
||||||
local record = entity_index_try_get(world.entity_index, entity)
|
|
||||||
|
|
||||||
if not record then return empty_table end
|
|
||||||
local archetype = record.archetype
|
|
||||||
if not archetype then return empty_table end
|
|
||||||
|
|
||||||
local components = {}
|
|
||||||
for _, ty in archetype.types do
|
|
||||||
table.insert(components, ty)
|
|
||||||
end
|
|
||||||
return components
|
|
||||||
end
|
|
||||||
|
|
||||||
local function convert_component(world, debug, entity): string
|
|
||||||
if IS_PAIR(entity) then
|
|
||||||
local left = convert_component(world, debug, pair_first(world, entity))
|
|
||||||
local right = convert_component(world, debug, pair_second(world, entity))
|
|
||||||
return `({left}, {right})`
|
|
||||||
else
|
|
||||||
return world:get(entity, debug) or `${tostring(entity)}`
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Indicates a value is a tag
|
|
||||||
local TAG = newproxy()
|
|
||||||
--- Indicates a value is set to nil; this is not allowed in 0.3.0
|
|
||||||
local BAD_VALUE = newproxy()
|
|
||||||
|
|
||||||
local function get_component(ctype_name: string, map_components: {[string]: number})
|
|
||||||
|
|
||||||
local function get_entity(ctype: query_parser.PureComponent)
|
|
||||||
local value = ctype.value
|
|
||||||
|
|
||||||
if value.type == "Entity" then
|
|
||||||
return value.entity
|
|
||||||
elseif value.type == "Name" then
|
|
||||||
return map_components[value.name]
|
|
||||||
end
|
|
||||||
error("bad")
|
|
||||||
end
|
|
||||||
|
|
||||||
local entity_to_set
|
|
||||||
local parse = query_parser(ctype_name)[1]
|
|
||||||
if parse.type == "Component" then
|
|
||||||
entity_to_set = get_entity(parse)
|
|
||||||
elseif parse.type == "Relationship" then
|
|
||||||
local left, right = jecs.Wildcard, jecs.Wildcard
|
|
||||||
|
|
||||||
if parse.left.type == "Component" then
|
|
||||||
left = get_entity(parse.left)
|
|
||||||
end
|
|
||||||
|
|
||||||
if parse.right.type == "Component" then
|
|
||||||
right = get_entity(parse.right)
|
|
||||||
end
|
|
||||||
|
|
||||||
entity_to_set = pair(left, right)
|
|
||||||
end
|
|
||||||
|
|
||||||
return entity_to_set
|
|
||||||
end
|
|
||||||
|
|
||||||
return function()
|
|
||||||
|
|
||||||
local inspect_entity = queue(remotes.inspect_entity)
|
|
||||||
local update_inspect_settings = queue(remotes.update_inspect_settings)
|
|
||||||
local stop_inspect_entity = queue(remotes.stop_inspect_entity)
|
|
||||||
local update_entity = queue(remotes.update_entity)
|
|
||||||
local delete_entity = queue(remotes.delete_entity)
|
|
||||||
|
|
||||||
local get_entity_component = queue(remotes.get_component)
|
|
||||||
local validate_entity = queue(remotes.validate_entity_component)
|
|
||||||
|
|
||||||
local inspectors = {}
|
|
||||||
|
|
||||||
return function()
|
|
||||||
|
|
||||||
for incoming, world_id, ctype_name in validate_entity:iter() do
|
|
||||||
local world: types.World = public[world_id]
|
|
||||||
local outgoing = reverse_connector(incoming)
|
|
||||||
|
|
||||||
if not traffic_check.check_no_wl(incoming.host) then continue end
|
|
||||||
if not world or world.class_name ~= "World" then continue end
|
|
||||||
|
|
||||||
local map_components = {}
|
|
||||||
for id, name in world.world:query(jecs.Name):iter() do
|
|
||||||
map_components[name] = id
|
|
||||||
end
|
|
||||||
|
|
||||||
local ok, reason = pcall(get_component, ctype_name, map_components)
|
|
||||||
|
|
||||||
remotes.validate_entity_component_result:fire(
|
|
||||||
outgoing, world_id, ctype_name, ok, not ok and reason or nil
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
for incoming, world_id, entity, inspect_id in inspect_entity:iter() do
|
|
||||||
local world: types.World = public[world_id]
|
|
||||||
local outgoing = reverse_connector(incoming)
|
|
||||||
|
|
||||||
if not traffic_check.check_no_wl(incoming.host) then continue end
|
|
||||||
if not world or world.class_name ~= "World" then continue end
|
|
||||||
|
|
||||||
inspectors[inspect_id] = {
|
|
||||||
outgoing = outgoing,
|
|
||||||
world = world,
|
|
||||||
entity = entity,
|
|
||||||
paused = false,
|
|
||||||
|
|
||||||
new_values = {},
|
|
||||||
old_values = {}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
for incoming, inspect_id in stop_inspect_entity:iter() do
|
|
||||||
if not traffic_check.check_no_wl(incoming.host) then continue end
|
|
||||||
inspectors[inspect_id] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
for incoming, inspect_id in delete_entity:iter() do
|
|
||||||
if not traffic_check.check_no_wl(incoming.host) then continue end
|
|
||||||
local inspect_data = inspectors[inspect_id]
|
|
||||||
local world_data = inspect_data.world
|
|
||||||
local world = world_data.world
|
|
||||||
local entity = inspect_data.entity
|
|
||||||
|
|
||||||
world:delete(entity)
|
|
||||||
end
|
|
||||||
|
|
||||||
for incoming, inspect_id, settings in update_inspect_settings:iter() do
|
|
||||||
if not traffic_check.check_no_wl(incoming.host) then continue end
|
|
||||||
local inspect_data = inspectors[inspect_id]
|
|
||||||
if not inspect_data then continue end
|
|
||||||
inspect_data.paused = settings.paused
|
|
||||||
end
|
|
||||||
|
|
||||||
for incoming, inspect_id, component in get_entity_component:iter() do
|
|
||||||
if not traffic_check.check_no_wl(incoming.host) then continue end
|
|
||||||
local inspect_data = inspectors[inspect_id]
|
|
||||||
local world_data = inspect_data.world
|
|
||||||
local world = world_data.world
|
|
||||||
local entity = inspect_data.entity
|
|
||||||
local to = reverse_connector(incoming)
|
|
||||||
|
|
||||||
local map_components = {}
|
|
||||||
for id, name in world:query(jecs.Name):iter() do
|
|
||||||
map_components[name] = id
|
|
||||||
end
|
|
||||||
|
|
||||||
local ok, component = pcall(get_component, component, map_components)
|
|
||||||
|
|
||||||
if component and ok then
|
|
||||||
remotes.return_component:fire(to, inspect_id, component, lon.output(world:get(entity, component), true, false))
|
|
||||||
else
|
|
||||||
remotes.return_component:fire(to, inspect_id, component, "nil")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for incoming, inspect_id, changes in update_entity:iter() do
|
|
||||||
if not traffic_check.check_no_wl(incoming.host) then continue end
|
|
||||||
local inspect_data = inspectors[inspect_id]
|
|
||||||
local world_data = inspect_data.world
|
|
||||||
local world = world_data.world
|
|
||||||
local entity = inspect_data.entity
|
|
||||||
|
|
||||||
local map_components = {}
|
|
||||||
for id, name in world:query(jecs.Name):iter() do
|
|
||||||
map_components[name] = id
|
|
||||||
end
|
|
||||||
|
|
||||||
for ctype_name, value in changes do
|
|
||||||
|
|
||||||
-- get the component we need to set
|
|
||||||
local ok, entity_to_set = pcall(get_component, ctype_name, map_components)
|
|
||||||
local old = world:get(entity, entity_to_set)
|
|
||||||
|
|
||||||
if not ok then
|
|
||||||
warn("attempted to set", ctype_name, "to", value)
|
|
||||||
warn(entity_to_set)
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
|
|
||||||
local ok, result = pcall(lon.parse, value)
|
|
||||||
|
|
||||||
if not ok then
|
|
||||||
warn("attempted to set", ctype_name, "to", value)
|
|
||||||
warn(result)
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
|
|
||||||
local ok, data = pcall(
|
|
||||||
lon.compile,
|
|
||||||
result,
|
|
||||||
{ tag = TAG, old = old }
|
|
||||||
)
|
|
||||||
|
|
||||||
if not ok then
|
|
||||||
warn("attempted to set", ctype_name, "to", value)
|
|
||||||
warn(data)
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
|
|
||||||
if data == nil then
|
|
||||||
world:remove(entity, entity_to_set)
|
|
||||||
elseif data == TAG then
|
|
||||||
-- trying to use world:set with a tag will result in an error,
|
|
||||||
-- even if the data is nil, so instead we use world:add
|
|
||||||
world:add(entity, entity_to_set)
|
|
||||||
else
|
|
||||||
world:set(entity, entity_to_set, data)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for inspect_id, inspector_data in inspectors do
|
|
||||||
local world = inspector_data.world.world
|
|
||||||
local entity = inspector_data.entity
|
|
||||||
|
|
||||||
if inspector_data.paused then continue end
|
|
||||||
if world:contains(entity) == false then continue end
|
|
||||||
|
|
||||||
local new_values = inspector_data.new_values
|
|
||||||
local old_values = inspector_data.old_values
|
|
||||||
|
|
||||||
local components = get_all_components(world, entity)
|
|
||||||
|
|
||||||
local function is_tag(id: jecs.Entity<any>)
|
|
||||||
return jecs.is_tag(world, id)
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, component in components do
|
|
||||||
local name = convert_component(world, jecs.Name, component)
|
|
||||||
local is_tag = is_tag(component)
|
|
||||||
|
|
||||||
if is_tag then
|
|
||||||
new_values[name] = TAG
|
|
||||||
else
|
|
||||||
local value = world:get(entity, component)
|
|
||||||
new_values[name] = if value == nil then BAD_VALUE else value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for name, new_value in new_values do
|
|
||||||
local old_value = old_values[name]
|
|
||||||
|
|
||||||
if old_value == new_value and typeof(new_value) ~= "table" then continue end
|
|
||||||
remotes.inspect_entity_update:fire(
|
|
||||||
inspector_data.outgoing,
|
|
||||||
inspect_id,
|
|
||||||
name,
|
|
||||||
if new_value == TAG then "tag"
|
|
||||||
--todo: figure out a better way to say that you are not allowed to store nil in a component
|
|
||||||
elseif new_value == BAD_VALUE then "nil (not allowed)"
|
|
||||||
else lon.output(new_value, true, true)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
for name, value in old_values do
|
|
||||||
local new_value = new_values[name]
|
|
||||||
|
|
||||||
if new_value ~= nil then continue end
|
|
||||||
remotes.inspect_entity_update:fire(
|
|
||||||
inspector_data.outgoing,
|
|
||||||
inspect_id,
|
|
||||||
name,
|
|
||||||
nil
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
table.clear(old_values)
|
|
||||||
|
|
||||||
inspector_data.new_values = old_values
|
|
||||||
inspector_data.old_values = new_values
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,209 +0,0 @@
|
||||||
local jecs = require(script.Parent.Parent.Parent.jecs)
|
|
||||||
local queue = require(script.Parent.Parent.Parent.modules.queue)
|
|
||||||
local remotes = require(script.Parent.Parent.Parent.modules.remotes)
|
|
||||||
local reverse_connector = require(script.Parent.Parent.Parent.modules.reverse_connector)
|
|
||||||
local traffic_check = require(script.Parent.Parent.Parent.modules.traffic_check)
|
|
||||||
local types = require(script.Parent.Parent.Parent.modules.types)
|
|
||||||
local public = require(script.Parent.Parent.public)
|
|
||||||
|
|
||||||
local entity_index_try_get = jecs.entity_index_try_get
|
|
||||||
local IS_PAIR = jecs.IS_PAIR
|
|
||||||
local pair_first = jecs.pair_first
|
|
||||||
local pair_second = jecs.pair_second
|
|
||||||
local empty_table = {}
|
|
||||||
|
|
||||||
|
|
||||||
local function convert_component(world, debug, entity): string
|
|
||||||
if IS_PAIR(entity) then
|
|
||||||
local left = convert_component(world, debug, pair_first(world, entity))
|
|
||||||
local right = convert_component(world, debug, pair_second(world, entity))
|
|
||||||
return `({left}, {right})`
|
|
||||||
else
|
|
||||||
return world:get(entity, debug) or `${tostring(entity)}`
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_type(t: { [any]: any }): string
|
|
||||||
local key, value = next(t)
|
|
||||||
if key == nil then return "" end
|
|
||||||
return `[{typeof(key)}]: {typeof(value)}`
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_string_keys(t: { [any]: any }): ({ string }, boolean)
|
|
||||||
local keys = {}
|
|
||||||
local i = 0
|
|
||||||
for key in t do
|
|
||||||
if i > 3 then return keys, true end
|
|
||||||
if typeof(key) ~= "string" then continue end
|
|
||||||
table.insert(keys, key)
|
|
||||||
i += 1
|
|
||||||
end
|
|
||||||
return keys, false
|
|
||||||
end
|
|
||||||
|
|
||||||
local function is_tag(world: jecs.World, id: jecs.Entity<any>)
|
|
||||||
return jecs.is_tag(world, id)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_all_components(world, entity): {}
|
|
||||||
local record = entity_index_try_get(world.entity_index, entity)
|
|
||||||
|
|
||||||
if not record then return empty_table end
|
|
||||||
local archetype = record.archetype
|
|
||||||
if not archetype then return empty_table end
|
|
||||||
|
|
||||||
local components = {}
|
|
||||||
for _, ty in archetype.types do
|
|
||||||
table.insert(components, ty)
|
|
||||||
end
|
|
||||||
|
|
||||||
table.sort(components, function(a, b)
|
|
||||||
return if is_tag(world, a) and is_tag(world, b) then a < b
|
|
||||||
elseif is_tag(world, a) then true
|
|
||||||
elseif is_tag(world, b) then false
|
|
||||||
else a < b
|
|
||||||
end)
|
|
||||||
|
|
||||||
return components
|
|
||||||
end
|
|
||||||
|
|
||||||
local function obtain_string(entity: jecs.Entity<any>, world: jecs.World)
|
|
||||||
local MAX_SIZE = 840
|
|
||||||
local has_more = false
|
|
||||||
local entity_name = world:get(entity, jecs.Name)
|
|
||||||
local strings = {`<b>{if entity_name then `{entity_name} #` else "#"}{entity}</b>\n`}
|
|
||||||
local n = #strings[1]
|
|
||||||
|
|
||||||
for _, id in get_all_components(world, entity) do
|
|
||||||
if id == jecs.Name then continue end
|
|
||||||
local name = convert_component(world, jecs.Name, id)
|
|
||||||
local value = if is_tag(world, id) then nil else world:get(entity, id)
|
|
||||||
local to_append
|
|
||||||
|
|
||||||
if typeof(value) == "table" then
|
|
||||||
local string_keys = get_string_keys(value)
|
|
||||||
|
|
||||||
if #string_keys > 0 then
|
|
||||||
local temp_b = {`<b>{name}</b>:`}
|
|
||||||
local temp_n = #temp_b[1]
|
|
||||||
|
|
||||||
for key, value in pairs(value) do
|
|
||||||
if #temp_b > 0 then
|
|
||||||
table.insert(temp_b, "\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
local str_of_v = if type(value) == "string" then `{value}`
|
|
||||||
elseif typeof(value) == "table" then get_type(value)
|
|
||||||
else tostring(value)
|
|
||||||
if #str_of_v > 32 then
|
|
||||||
str_of_v = `{string.sub(str_of_v, 1, 30)}..`
|
|
||||||
end
|
|
||||||
|
|
||||||
local str = `{key}: {str_of_v}`
|
|
||||||
|
|
||||||
if temp_n + #str + 2 > 32 then
|
|
||||||
table.insert(temp_b, "...")
|
|
||||||
break
|
|
||||||
else
|
|
||||||
table.insert(temp_b, str)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
to_append = `{table.concat(temp_b)}`
|
|
||||||
else
|
|
||||||
to_append = `<b>{name}</b>: {get_type(value)}`
|
|
||||||
end
|
|
||||||
elseif is_tag(world, id) then
|
|
||||||
to_append = `<b>{name}</b>`
|
|
||||||
else
|
|
||||||
local value = tostring(value)
|
|
||||||
if #value > 32 then
|
|
||||||
to_append = `<b>{name}</b>: {string.sub(value, 1, 30)}..`
|
|
||||||
else
|
|
||||||
to_append = `<b>{name}</b>: {value}`
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if MAX_SIZE > n + #to_append then
|
|
||||||
n += #to_append
|
|
||||||
table.insert(strings, to_append)
|
|
||||||
else
|
|
||||||
has_more = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local str = table.concat(strings, "\n")
|
|
||||||
if has_more then str = str .. "..." end
|
|
||||||
|
|
||||||
return str
|
|
||||||
end
|
|
||||||
|
|
||||||
return function()
|
|
||||||
|
|
||||||
local send_mouse_pointer = queue(remotes.send_mouse_pointer)
|
|
||||||
|
|
||||||
return function()
|
|
||||||
|
|
||||||
for incoming, world_id, pos, dir in send_mouse_pointer:iter() do
|
|
||||||
if not traffic_check.check_no_wl(incoming.host) then continue end
|
|
||||||
local world_data: types.World = public[world_id]
|
|
||||||
local world = world_data.world
|
|
||||||
local outgoing = reverse_connector(incoming)
|
|
||||||
|
|
||||||
if world_data.entities == nil and world_data.get_entity_from_part == nil then continue end
|
|
||||||
if not world_data or world_data.class_name ~= "World" then continue end
|
|
||||||
|
|
||||||
local result = workspace:Raycast(pos, dir * 1000)
|
|
||||||
|
|
||||||
if not result then
|
|
||||||
remotes.send_mouse_entity:fire(
|
|
||||||
outgoing,
|
|
||||||
world_id
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
|
|
||||||
local part = result.Instance
|
|
||||||
local entity
|
|
||||||
|
|
||||||
-- no way to obtain the entity
|
|
||||||
if world_data.get_entity_from_part == nil and world_data.entities == nil then
|
|
||||||
remotes.send_mouse_entity:fire(
|
|
||||||
outgoing,
|
|
||||||
world_id
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
|
|
||||||
if world_data.get_entity_from_part == nil then
|
|
||||||
entity = world_data.entities[part]
|
|
||||||
|
|
||||||
while entity == nil and part.Parent ~= game do
|
|
||||||
part, entity = part.Parent, world_data.entities[part]
|
|
||||||
end
|
|
||||||
else
|
|
||||||
entity, part = world_data.get_entity_from_part(part)
|
|
||||||
end
|
|
||||||
|
|
||||||
if not entity then
|
|
||||||
remotes.send_mouse_entity:fire(
|
|
||||||
outgoing,
|
|
||||||
world_id
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
|
|
||||||
local str = obtain_string(entity, world, jecs.Name)
|
|
||||||
|
|
||||||
remotes.send_mouse_entity:fire(
|
|
||||||
outgoing,
|
|
||||||
world_id,
|
|
||||||
part,
|
|
||||||
entity,
|
|
||||||
str
|
|
||||||
)
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
local Players = game:GetService("Players")
|
|
||||||
|
|
||||||
local net = require(script.Parent.Parent.Parent.modules.net)
|
|
||||||
local queue = require(script.Parent.Parent.Parent.modules.queue)
|
|
||||||
local remotes = require(script.Parent.Parent.Parent.modules.remotes)
|
|
||||||
local reverse_connector = require(script.Parent.Parent.Parent.modules.reverse_connector)
|
|
||||||
local traffic_check = require(script.Parent.Parent.Parent.modules.traffic_check)
|
|
||||||
|
|
||||||
|
|
||||||
return function()
|
|
||||||
|
|
||||||
local ping = queue(remotes.ping)
|
|
||||||
|
|
||||||
for _, player in Players:GetPlayers() do
|
|
||||||
if traffic_check.communication_is_allowed(net.local_host, player, true) then
|
|
||||||
remotes.new_server_registered:fire({
|
|
||||||
host = player,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return function()
|
|
||||||
for connector in ping:iter() do
|
|
||||||
local outgoing = reverse_connector(connector)
|
|
||||||
remotes.new_server_registered:fire(outgoing)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
local queue = require(script.Parent.Parent.Parent.modules.queue)
|
|
||||||
local remotes = require(script.Parent.Parent.Parent.modules.remotes)
|
|
||||||
local reverse_connector = require(script.Parent.Parent.Parent.modules.reverse_connector)
|
|
||||||
local traffic_check = require(script.Parent.Parent.Parent.modules.traffic_check)
|
|
||||||
local public = require(script.Parent.Parent.public)
|
|
||||||
|
|
||||||
return function()
|
|
||||||
|
|
||||||
local connected = {}
|
|
||||||
|
|
||||||
local bind_to_core = queue(remotes.bind_to_server_core)
|
|
||||||
|
|
||||||
return function()
|
|
||||||
|
|
||||||
for connector in bind_to_core:iter() do
|
|
||||||
local outgoing = reverse_connector(connector)
|
|
||||||
|
|
||||||
if not traffic_check.check_no_wl(connector.host) then continue end
|
|
||||||
-- print("help")
|
|
||||||
|
|
||||||
table.insert(connected, outgoing)
|
|
||||||
local schedulers = {}
|
|
||||||
local worlds = {}
|
|
||||||
|
|
||||||
for idx, data in ipairs(public) do
|
|
||||||
|
|
||||||
if data.class_name == "Scheduler" then
|
|
||||||
table.insert(schedulers, {
|
|
||||||
name = data.name :: string,
|
|
||||||
id = idx
|
|
||||||
})
|
|
||||||
elseif data.class_name == "World" then
|
|
||||||
table.insert(worlds, {
|
|
||||||
name = data.name :: string,
|
|
||||||
id = idx
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
remotes.update_server_data:fire(outgoing, {
|
|
||||||
schedulers = schedulers,
|
|
||||||
worlds = worlds
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
if public.updated == false then return end
|
|
||||||
public.updated = false
|
|
||||||
|
|
||||||
local schedulers = {}
|
|
||||||
local worlds = {}
|
|
||||||
|
|
||||||
for idx, data in ipairs(public) do
|
|
||||||
|
|
||||||
if data.class_name == "Scheduler" then
|
|
||||||
table.insert(schedulers, {
|
|
||||||
name = data.name :: string,
|
|
||||||
id = idx
|
|
||||||
})
|
|
||||||
elseif data.class_name == "World" then
|
|
||||||
table.insert(worlds, {
|
|
||||||
name = data.name :: string,
|
|
||||||
id = idx
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
local fired_to = {}
|
|
||||||
for _, connector in connected do
|
|
||||||
if fired_to[connector] then return end
|
|
||||||
fired_to[connector] = true
|
|
||||||
remotes.update_server_data:fire(connector, {
|
|
||||||
schedulers = schedulers,
|
|
||||||
worlds = worlds
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,503 +0,0 @@
|
||||||
local jecs = require(script.Parent.Parent.Parent.jecs)
|
|
||||||
local queue = require(script.Parent.Parent.Parent.modules.queue)
|
|
||||||
local remotes = require(script.Parent.Parent.Parent.modules.remotes)
|
|
||||||
local reverse_connector = require(script.Parent.Parent.Parent.modules.reverse_connector)
|
|
||||||
local traffic_check = require(script.Parent.Parent.Parent.modules.traffic_check)
|
|
||||||
local types = require(script.Parent.Parent.Parent.modules.types)
|
|
||||||
local public = require(script.Parent.Parent.public)
|
|
||||||
local query_parser = require(script.Parent.Parent.query_parser)
|
|
||||||
|
|
||||||
type Connection = {
|
|
||||||
|
|
||||||
outgoing: types.OutgoingConnector,
|
|
||||||
query_id: number,
|
|
||||||
frame: number,
|
|
||||||
|
|
||||||
paused: boolean,
|
|
||||||
refresh: boolean,
|
|
||||||
|
|
||||||
world: types.World,
|
|
||||||
|
|
||||||
include: {jecs.Entity<any>},
|
|
||||||
exclude: {jecs.Entity<any>},
|
|
||||||
with: {jecs.Entity<any>},
|
|
||||||
|
|
||||||
new_columns: {{any}},
|
|
||||||
old_columns: {{any}},
|
|
||||||
|
|
||||||
from: number,
|
|
||||||
upto: number
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
local NIL = newproxy() -- NULL is displayed if the value exists, buth as no value
|
|
||||||
|
|
||||||
local function clear_columns(columns: {{any}})
|
|
||||||
for _, column in columns do
|
|
||||||
local name = column[1]
|
|
||||||
table.clear(column)
|
|
||||||
column[1] = name
|
|
||||||
assert(column[1] == name)
|
|
||||||
end
|
|
||||||
return columns
|
|
||||||
end
|
|
||||||
|
|
||||||
local function reverse_columns(columns: {{any}}, size: number)
|
|
||||||
for _, column in columns do
|
|
||||||
for i = 0, size // 2 - 1 do
|
|
||||||
column[i + 2], column[(size + 1) - i] = column[(size + 1) - i], column[i + 2]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return columns
|
|
||||||
end
|
|
||||||
|
|
||||||
return function()
|
|
||||||
|
|
||||||
local processing_queries: {[number]: Connection} = {}
|
|
||||||
|
|
||||||
local validate_query = queue(remotes.validate_query)
|
|
||||||
local request_query = queue(remotes.request_query)
|
|
||||||
local disconnect_query = queue(remotes.disconnect_query)
|
|
||||||
local advance_query_page = queue(remotes.advance_query_page)
|
|
||||||
local pause_query = queue(remotes.pause_query)
|
|
||||||
local refresh_query = queue(remotes.refresh_results)
|
|
||||||
|
|
||||||
local function check_if_query_valid(world: types.World, query: string): (boolean, string)
|
|
||||||
local map_components = {}
|
|
||||||
local ok, result = pcall(query_parser, query)
|
|
||||||
local msg = nil
|
|
||||||
|
|
||||||
if not ok then
|
|
||||||
return ok, result :: any
|
|
||||||
end
|
|
||||||
|
|
||||||
for id, name in world.world:query(jecs.Name):iter() do
|
|
||||||
map_components[name] = id
|
|
||||||
end
|
|
||||||
|
|
||||||
local total_to_query = 0
|
|
||||||
|
|
||||||
for _, ctype in result do
|
|
||||||
if not ok then break end
|
|
||||||
|
|
||||||
if ctype.query and not ctype.exclude then
|
|
||||||
total_to_query += 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if ctype.type == "Component" then
|
|
||||||
if ctype.value.type == "Entity" then
|
|
||||||
if world.world:contains(ctype.value.entity) then continue end
|
|
||||||
return false, "entity does not exist"
|
|
||||||
elseif ctype.value.type == "Name" then
|
|
||||||
if map_components[ctype.value.name] then continue end
|
|
||||||
return false, `unknown component called {ctype.value.name}`
|
|
||||||
else
|
|
||||||
return false, "what"
|
|
||||||
end
|
|
||||||
elseif ctype.type == "Relationship" then
|
|
||||||
local both_wildcard = ctype.left.type == "Wildcard" and ctype.right.type == "Wildcard"
|
|
||||||
if both_wildcard then
|
|
||||||
return false, `(*, *) is not a valid relationship`
|
|
||||||
end
|
|
||||||
|
|
||||||
local left = ctype.left
|
|
||||||
local right = ctype.right
|
|
||||||
|
|
||||||
if left.type == "Component" then
|
|
||||||
if left.value.type == "Entity" then
|
|
||||||
if world.world:contains(left.value.entity) then continue end
|
|
||||||
return false, "entity does not exist"
|
|
||||||
elseif left.value.type == "Name" then
|
|
||||||
if map_components[left.value.name] then continue end
|
|
||||||
return false, `unknown component called {left.value.name}`
|
|
||||||
else
|
|
||||||
return false, "what"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if right.type == "Component" then
|
|
||||||
if right.value.type == "Entity" then
|
|
||||||
if world.world:contains(right.value.entity) then continue end
|
|
||||||
return false, "entity does not exist"
|
|
||||||
elseif right.value.type == "Name" then
|
|
||||||
if map_components[right.value.name] then continue end
|
|
||||||
return false, `unknown component called {right.value.name}`
|
|
||||||
else
|
|
||||||
return false, "what"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if total_to_query > 26 then
|
|
||||||
warn("attempting to observe too many values")
|
|
||||||
return false, "attempting to observe too many entities"
|
|
||||||
end
|
|
||||||
|
|
||||||
return ok, msg
|
|
||||||
end
|
|
||||||
|
|
||||||
--fixme: contains is missing from types
|
|
||||||
local function check_if_still_valid(world: any, entities: {any})
|
|
||||||
for _, id in entities do
|
|
||||||
if jecs.IS_PAIR(id) then
|
|
||||||
if not (world:contains(jecs.pair_first(world, id) and jecs.pair_second(world, id))) then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
elseif not world:contains(id) then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_terms(query: string, world: jecs.World)
|
|
||||||
local result = query_parser(query)
|
|
||||||
local include = {}
|
|
||||||
local exclude = {}
|
|
||||||
local with = {}
|
|
||||||
|
|
||||||
local map_components = {}
|
|
||||||
local map_entity: {[any]: any} = {}
|
|
||||||
for id, name in world:query(jecs.Name):iter() do
|
|
||||||
map_components[name] = id
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_entity(ctype: query_parser.PureComponent)
|
|
||||||
local value = ctype.value
|
|
||||||
|
|
||||||
if value.type == "Entity" then
|
|
||||||
return value.entity
|
|
||||||
elseif value.type == "Name" then
|
|
||||||
return map_components[value.name]
|
|
||||||
end
|
|
||||||
error("bad")
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, ctype in result do
|
|
||||||
if ctype.type == "Component" then
|
|
||||||
map_entity[ctype] = get_entity(ctype)
|
|
||||||
elseif ctype.type == "Relationship" then
|
|
||||||
local left, right = jecs.Wildcard, jecs.Wildcard
|
|
||||||
|
|
||||||
if ctype.left.type == "Component" then
|
|
||||||
left = get_entity(ctype.left)
|
|
||||||
end
|
|
||||||
|
|
||||||
if ctype.right.type == "Component" then
|
|
||||||
right = get_entity(ctype.right)
|
|
||||||
end
|
|
||||||
|
|
||||||
local pair = jecs.pair(left, right)
|
|
||||||
map_entity[ctype] = pair
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, ctype in result do
|
|
||||||
local entity = map_entity[ctype]
|
|
||||||
if ctype.exclude then
|
|
||||||
table.insert(exclude, entity)
|
|
||||||
elseif ctype.query then
|
|
||||||
-- local name = if ctype.type == "Component" then ctype.name else `({ctype.left.name}, {ctype.right.name})`
|
|
||||||
table.insert(include, entity)
|
|
||||||
else
|
|
||||||
table.insert(with, entity)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return include, exclude, with
|
|
||||||
end
|
|
||||||
|
|
||||||
return function()
|
|
||||||
|
|
||||||
for incoming, world_id, query in validate_query:iter() do
|
|
||||||
if not traffic_check.check_no_wl(incoming.host) then continue end
|
|
||||||
|
|
||||||
local world: types.World = public[world_id]
|
|
||||||
local outgoing = reverse_connector(incoming)
|
|
||||||
|
|
||||||
if not world or world.class_name ~= "World" then
|
|
||||||
remotes.validate_result:fire(outgoing, world_id, query, nil, false, "world does not exist")
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
|
|
||||||
local ok, message = check_if_query_valid(world, query)
|
|
||||||
local include, exclude, with
|
|
||||||
|
|
||||||
if ok then include, exclude, with = get_terms(query, world.world) end
|
|
||||||
|
|
||||||
remotes.validate_result:fire(outgoing, world_id, query, ok and {
|
|
||||||
include = include,
|
|
||||||
exclude = exclude,
|
|
||||||
with = with
|
|
||||||
}, ok, message)
|
|
||||||
end
|
|
||||||
|
|
||||||
for incoming, query_id in disconnect_query:iter() do
|
|
||||||
processing_queries[query_id] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
for incoming, world_id, query_id, query in request_query:iter() do
|
|
||||||
if not traffic_check.check_no_wl(incoming.host) then continue end
|
|
||||||
|
|
||||||
local world: types.World = public[world_id]
|
|
||||||
local outgoing = reverse_connector(incoming)
|
|
||||||
|
|
||||||
if not world or world.class_name ~= "World" then continue end
|
|
||||||
|
|
||||||
local ok = check_if_query_valid(world, query)
|
|
||||||
|
|
||||||
if not ok then continue end
|
|
||||||
|
|
||||||
local include, exclude, with = get_terms(query, world.world)
|
|
||||||
local new_columns = {}
|
|
||||||
local old_columns = {}
|
|
||||||
|
|
||||||
table.insert(new_columns, {})
|
|
||||||
table.insert(old_columns, {})
|
|
||||||
|
|
||||||
for _, ctype in include do
|
|
||||||
table.insert(new_columns, {})
|
|
||||||
table.insert(old_columns, {})
|
|
||||||
end
|
|
||||||
|
|
||||||
if processing_queries[query_id] then
|
|
||||||
local connection = processing_queries[query_id]
|
|
||||||
|
|
||||||
connection.outgoing = outgoing
|
|
||||||
connection.query_id = query_id
|
|
||||||
connection.world = world
|
|
||||||
connection.refresh = true
|
|
||||||
|
|
||||||
connection.include = include
|
|
||||||
connection.exclude = exclude
|
|
||||||
connection.with = with
|
|
||||||
|
|
||||||
connection.new_columns = new_columns
|
|
||||||
connection.old_columns = old_columns
|
|
||||||
connection.from = 1
|
|
||||||
connection.upto = 25
|
|
||||||
else
|
|
||||||
local connection: Connection = {
|
|
||||||
|
|
||||||
outgoing = outgoing,
|
|
||||||
query_id = query_id,
|
|
||||||
frame = 0,
|
|
||||||
|
|
||||||
world = world,
|
|
||||||
|
|
||||||
paused = false,
|
|
||||||
refresh = false,
|
|
||||||
|
|
||||||
include = include,
|
|
||||||
exclude = exclude,
|
|
||||||
with = with,
|
|
||||||
|
|
||||||
new_columns = new_columns,
|
|
||||||
old_columns = old_columns,
|
|
||||||
|
|
||||||
from = 1,
|
|
||||||
upto = 25
|
|
||||||
}
|
|
||||||
|
|
||||||
processing_queries[query_id] = connection
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for incoming, query_id in refresh_query:iter() do
|
|
||||||
if not traffic_check.check_no_wl(incoming.host) then continue end
|
|
||||||
local query = processing_queries[query_id]
|
|
||||||
if not query then continue end
|
|
||||||
query.refresh = true
|
|
||||||
end
|
|
||||||
|
|
||||||
for incoming, query_id, state in pause_query:iter() do
|
|
||||||
if not traffic_check.check_no_wl(incoming.host) then continue end
|
|
||||||
local query = processing_queries[query_id]
|
|
||||||
if not query then continue end
|
|
||||||
query.paused = state
|
|
||||||
end
|
|
||||||
|
|
||||||
for incoming, query_id, from, to in advance_query_page:iter() do
|
|
||||||
if not traffic_check.check_no_wl(incoming.host) then continue end
|
|
||||||
local query = processing_queries[query_id]
|
|
||||||
if not query then continue end
|
|
||||||
|
|
||||||
query.refresh = true
|
|
||||||
query.from = from
|
|
||||||
query.upto = to
|
|
||||||
end
|
|
||||||
|
|
||||||
for query_id, query_data in processing_queries do
|
|
||||||
if query_data.paused and query_data.refresh ~= true then continue end
|
|
||||||
debug.profilebegin("process query")
|
|
||||||
query_data.refresh = false
|
|
||||||
local world_data = query_data.world
|
|
||||||
local world = world_data.world
|
|
||||||
local debug_trait = jecs.Name
|
|
||||||
|
|
||||||
if
|
|
||||||
not (check_if_still_valid(world, query_data.include)
|
|
||||||
and check_if_still_valid(world, query_data.exclude)
|
|
||||||
and check_if_still_valid(world, query_data.with))
|
|
||||||
then
|
|
||||||
-- query is no longer valid!
|
|
||||||
--todo: query is invalid, notify the client about this
|
|
||||||
debug.profileend()
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
|
|
||||||
local query = world:query(unpack(query_data.include))
|
|
||||||
|
|
||||||
if #query_data.exclude > 0 then
|
|
||||||
query = query:without(unpack(query_data.exclude))
|
|
||||||
end
|
|
||||||
|
|
||||||
if #query_data.with > 0 then
|
|
||||||
query = query:with(unpack(query_data.with))
|
|
||||||
end
|
|
||||||
|
|
||||||
local from = query_data.from
|
|
||||||
local upto = query_data.upto
|
|
||||||
|
|
||||||
local new_columns = query_data.new_columns
|
|
||||||
local old_columns = query_data.old_columns
|
|
||||||
-- set the names of each column
|
|
||||||
--todo: fix type
|
|
||||||
local function get_name(entity: any)
|
|
||||||
if jecs.IS_PAIR(entity) then
|
|
||||||
local left = jecs.pair_first(world, entity)
|
|
||||||
local right = jecs.pair_second(world, entity)
|
|
||||||
return `({get_name(left)}, {get_name(right)})`
|
|
||||||
elseif entity == jecs.Wildcard :: any then
|
|
||||||
return "*"
|
|
||||||
elseif world:has(entity, debug_trait) then
|
|
||||||
return world:get(entity, debug_trait)
|
|
||||||
else
|
|
||||||
return `${entity}`
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- set column names
|
|
||||||
for index, column in new_columns do
|
|
||||||
local e = query_data.include[index - 1]
|
|
||||||
|
|
||||||
if e then
|
|
||||||
column[1] = get_name(e)
|
|
||||||
else
|
|
||||||
column[1] = "id"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- process the data into columns
|
|
||||||
-- we inline the query here, as jecs queries are in reverse to prevent iterator invalidation
|
|
||||||
-- this is usually fine, but it's annoying, as now entities are added to the first page.
|
|
||||||
--todo: pause button
|
|
||||||
local total_entities = 0
|
|
||||||
local archetypes = query:archetypes()
|
|
||||||
|
|
||||||
for _, archetype: jecs.Archetype in archetypes do
|
|
||||||
total_entities += #archetype.entities
|
|
||||||
end
|
|
||||||
|
|
||||||
local entities = table.create(total_entities)
|
|
||||||
local at = total_entities
|
|
||||||
local row_entity = 1
|
|
||||||
|
|
||||||
for _, archetype: jecs.Archetype in archetypes do
|
|
||||||
for row = #archetype.entities, 1, -1 do
|
|
||||||
local entity = archetype.entities[row]
|
|
||||||
table.insert(entities, entity)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
table.sort(entities)
|
|
||||||
|
|
||||||
for i = from, upto do
|
|
||||||
row_entity += 1
|
|
||||||
local entity = entities[i]
|
|
||||||
if not entity then continue end
|
|
||||||
new_columns[1][row_entity] = entity
|
|
||||||
for idx, ctype in query_data.include do
|
|
||||||
local value = world:get(entity, ctype)
|
|
||||||
new_columns[idx + 1][row_entity] = if value == nil then NIL else value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- reverse the order of each array
|
|
||||||
|
|
||||||
remotes.count_total_entities:fire(
|
|
||||||
query_data.outgoing,
|
|
||||||
query_id,
|
|
||||||
total_entities
|
|
||||||
)
|
|
||||||
|
|
||||||
-- diff the columns and replicate any new values
|
|
||||||
for column = 1, math.max(#new_columns, #old_columns) do
|
|
||||||
for row = 1, upto do
|
|
||||||
local new_value = new_columns[column][row]
|
|
||||||
local old_value = old_columns[column][row]
|
|
||||||
|
|
||||||
if new_value ~= old_value or typeof(new_value) == "table" then
|
|
||||||
-- todo: improve replication of the new value
|
|
||||||
-- ideally, we would figure out if the value is a certain type and needs special replication
|
|
||||||
-- if we for example determine a value is a string, or table, we cap it at MAX_CHARACTERS
|
|
||||||
-- or we tostring a couple keys of the table until we reach MAX_CHARACTERS.
|
|
||||||
-- we wanna be able to replicate every single. value
|
|
||||||
|
|
||||||
local MAX_CHARS = 750
|
|
||||||
local str
|
|
||||||
|
|
||||||
if typeof(new_value) == "string" then
|
|
||||||
str = `"{string.sub(new_value, 1, MAX_CHARS-2)}"`
|
|
||||||
elseif typeof(new_value) == "table" then
|
|
||||||
local temp_n = 0
|
|
||||||
local temp_b = {}
|
|
||||||
|
|
||||||
for key, value in new_value do
|
|
||||||
if #temp_b > 0 then
|
|
||||||
table.insert(temp_b, "; ")
|
|
||||||
end
|
|
||||||
|
|
||||||
local str_of_v = if type(value) == "string" then `"{value}"` else tostring(value)
|
|
||||||
local str = `{key}: {str_of_v}`
|
|
||||||
|
|
||||||
if temp_n + #str + 2 > MAX_CHARS then
|
|
||||||
table.insert(temp_b, "...")
|
|
||||||
break
|
|
||||||
else
|
|
||||||
table.insert(temp_b, str)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
str = `\{{table.concat(temp_b)}\}`
|
|
||||||
elseif new_value == NIL then
|
|
||||||
str = "" -- important distinction, this is still a valid component
|
|
||||||
elseif new_value == nil then
|
|
||||||
str = nil -- but this isnt
|
|
||||||
else
|
|
||||||
str = string.sub(tostring(new_value), 1, MAX_CHARS-2)
|
|
||||||
end
|
|
||||||
|
|
||||||
if row == 1 then str = new_value end
|
|
||||||
remotes.update_query_result:fire(
|
|
||||||
query_data.outgoing,
|
|
||||||
query_id,
|
|
||||||
query_data.frame,
|
|
||||||
column,
|
|
||||||
row,
|
|
||||||
str
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
query_data.new_columns = clear_columns(old_columns)
|
|
||||||
query_data.old_columns = new_columns
|
|
||||||
query_data.frame += 1
|
|
||||||
debug.profileend()
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,104 +0,0 @@
|
||||||
--!nolint LocalShadow
|
|
||||||
local hash = require(script.Parent.Parent.Parent.modules.hash_connector)
|
|
||||||
local queue = require(script.Parent.Parent.Parent.modules.queue)
|
|
||||||
local remotes = require(script.Parent.Parent.Parent.modules.remotes)
|
|
||||||
local reverse_connector = require(script.Parent.Parent.Parent.modules.reverse_connector)
|
|
||||||
local traffic_check = require(script.Parent.Parent.Parent.modules.traffic_check)
|
|
||||||
local types = require(script.Parent.Parent.Parent.modules.types)
|
|
||||||
local public = require(script.Parent.Parent.public)
|
|
||||||
|
|
||||||
return function()
|
|
||||||
|
|
||||||
local connected = {}
|
|
||||||
|
|
||||||
local request_scheduler = queue(remotes.request_scheduler)
|
|
||||||
local disconnect_scheduler = queue(remotes.disconnect_scheduler)
|
|
||||||
local schedule_pause = queue(remotes.scheduler_system_pause)
|
|
||||||
|
|
||||||
return function()
|
|
||||||
|
|
||||||
for incoming, id in request_scheduler:iter() do
|
|
||||||
if not traffic_check.check_no_wl(incoming.host) then continue end
|
|
||||||
|
|
||||||
local scheduler = public[id]
|
|
||||||
|
|
||||||
if scheduler.class_name ~= "Scheduler" then continue end
|
|
||||||
if scheduler == nil then continue end
|
|
||||||
|
|
||||||
local outgoing = reverse_connector(incoming)
|
|
||||||
connected[id] = connected[id] or {}
|
|
||||||
table.insert(connected[id], outgoing)
|
|
||||||
-- print("connected")
|
|
||||||
|
|
||||||
for system_id, data in scheduler.system_data do
|
|
||||||
remotes.scheduler_system_static_update:fire(outgoing, id, system_id, data)
|
|
||||||
end
|
|
||||||
|
|
||||||
for system_id, frames in scheduler.system_frames do
|
|
||||||
local frame = frames[1]
|
|
||||||
if not frame then continue end
|
|
||||||
remotes.scheduler_system_update:fire(outgoing, id, system_id, frame.i, frame.s)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for incoming, id in disconnect_scheduler:iter() do
|
|
||||||
if not traffic_check.check_no_wl(incoming.host) then continue end
|
|
||||||
|
|
||||||
if not connected[id] then continue end
|
|
||||||
|
|
||||||
local scheduler_connected = connected[id]
|
|
||||||
|
|
||||||
for i = #scheduler_connected, 1, -1 do
|
|
||||||
local connector = scheduler_connected[i]
|
|
||||||
if connector.host ~= incoming.host then continue end
|
|
||||||
if connector.to_vm ~= incoming.from_vm then continue end
|
|
||||||
scheduler_connected[i] = scheduler_connected[#scheduler_connected]
|
|
||||||
scheduler_connected[#scheduler_connected] = nil
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for incoming, id, system, paused in schedule_pause:iter() do
|
|
||||||
if not traffic_check.check_no_wl(incoming.host) then continue end
|
|
||||||
local scheduler: types.Scheduler = public[id]
|
|
||||||
|
|
||||||
if not scheduler then return end
|
|
||||||
|
|
||||||
scheduler:set_system_data(system, {
|
|
||||||
paused = paused
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
for id, connected in connected do
|
|
||||||
local scheduler: types.Scheduler = public[id]
|
|
||||||
if #connected == 0 then continue end
|
|
||||||
|
|
||||||
for system_id in scheduler.system_data_updated do
|
|
||||||
local map = {}
|
|
||||||
local data = scheduler.system_data[system_id]
|
|
||||||
|
|
||||||
for _, connector in connected do
|
|
||||||
if map[hash(connector)] then continue end
|
|
||||||
map[hash(connector)] = true
|
|
||||||
remotes.scheduler_system_static_update:fire(connector, id, system_id, data)
|
|
||||||
end
|
|
||||||
|
|
||||||
scheduler.system_data_updated[system_id] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
for system_id, frames in scheduler.system_frames_updated do
|
|
||||||
local map = {}
|
|
||||||
for frame in frames do
|
|
||||||
for _, connector in connected do
|
|
||||||
if map[hash(connector)] then continue end
|
|
||||||
map[hash(connector)] = true
|
|
||||||
remotes.scheduler_system_update:fire(connector, id, system_id, frame.i, frame.s)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
table.clear(frames)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,175 +0,0 @@
|
||||||
--[[
|
|
||||||
|
|
||||||
Handles the API for the system watch.
|
|
||||||
Users will be able to add watches to their systems to track changes.
|
|
||||||
|
|
||||||
Users will be able to learn about what actions a system performs on a jecs world
|
|
||||||
through this.
|
|
||||||
|
|
||||||
Hooked API's:
|
|
||||||
|
|
||||||
component()
|
|
||||||
entity()
|
|
||||||
remove()
|
|
||||||
clear()
|
|
||||||
delete()
|
|
||||||
add()
|
|
||||||
set()
|
|
||||||
|
|
||||||
]]
|
|
||||||
|
|
||||||
local jecs = require(script.Parent.Parent.Parent.jecs)
|
|
||||||
local hash_connector = require(script.Parent.Parent.Parent.modules.hash_connector)
|
|
||||||
local lon = require(script.Parent.Parent.Parent.modules.lon)
|
|
||||||
local queue = require(script.Parent.Parent.Parent.modules.queue)
|
|
||||||
local remotes = require(script.Parent.Parent.Parent.modules.remotes)
|
|
||||||
local reverse_connector = require(script.Parent.Parent.Parent.modules.reverse_connector)
|
|
||||||
local types = require(script.Parent.Parent.Parent.modules.types)
|
|
||||||
local public = require(script.Parent.Parent.public)
|
|
||||||
local watch = require(script.Parent.Parent.watch)
|
|
||||||
|
|
||||||
local NIL = watch.NIL
|
|
||||||
|
|
||||||
return function()
|
|
||||||
|
|
||||||
local stored_watches = {}
|
|
||||||
local connected_watches = {}
|
|
||||||
|
|
||||||
local function create_watch_for_id(
|
|
||||||
scheduler: types.Scheduler,
|
|
||||||
system: types.SystemId,
|
|
||||||
watch_id: number
|
|
||||||
)
|
|
||||||
local watch = scheduler:create_watch_for_system(system)
|
|
||||||
stored_watches[watch_id] = watch
|
|
||||||
end
|
|
||||||
|
|
||||||
local function send_watch_data_to(host: types.OutgoingConnector, watch_id: number, frame: number)
|
|
||||||
local map_worlds_to_name = {}
|
|
||||||
local watch = stored_watches[watch_id]
|
|
||||||
local frames = watch.watch.frames
|
|
||||||
local data = frames[frame]
|
|
||||||
|
|
||||||
if not data then
|
|
||||||
remotes.update_watch_data:fire(host, watch_id, frame, nil)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, world in ipairs(public) do
|
|
||||||
if world.world == nil then continue end
|
|
||||||
map_worlds_to_name[world.world] = jecs.Name
|
|
||||||
end
|
|
||||||
|
|
||||||
local to_send = {
|
|
||||||
types = data.types,
|
|
||||||
entities = data.entities,
|
|
||||||
component = table.clone(data.component),
|
|
||||||
values = table.clone(data.values)
|
|
||||||
}
|
|
||||||
|
|
||||||
for idx, ctype in to_send.component do
|
|
||||||
local world = data.worlds[idx]
|
|
||||||
to_send.component[idx] = world:get(ctype, map_worlds_to_name[world]) or ctype
|
|
||||||
end
|
|
||||||
|
|
||||||
for idx, value in to_send.values do
|
|
||||||
if value == NIL then to_send.values[idx] = "" end
|
|
||||||
to_send.values[idx] = lon.output(value, false)
|
|
||||||
end
|
|
||||||
|
|
||||||
remotes.update_watch_data:fire(host, watch_id, frame, to_send :: any)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function remove_watch_id(watch_id: number)
|
|
||||||
if not stored_watches[watch_id] then return end
|
|
||||||
stored_watches[watch_id].untrack()
|
|
||||||
stored_watches[watch_id] = nil
|
|
||||||
connected_watches[watch_id] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local function start_record_watch(watch_id: number)
|
|
||||||
local watch = stored_watches[watch_id]
|
|
||||||
watch.active = true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function stop_record_watch(watch_id: number)
|
|
||||||
local watch = stored_watches[watch_id]
|
|
||||||
watch.active = false
|
|
||||||
end
|
|
||||||
|
|
||||||
local function connect_watch(host: types.OutgoingConnector, watch_id: number)
|
|
||||||
connected_watches[watch_id] = connected_watches[watch_id] or {}
|
|
||||||
connected_watches[watch_id][hash_connector(host)] = host
|
|
||||||
|
|
||||||
local watch = stored_watches[watch_id]
|
|
||||||
for i, frame in watch.watch.frames do
|
|
||||||
remotes.update_overview:fire(host, watch_id, i, #frame.types)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function disconnect_watch(host: types.OutgoingConnector, watch_id: number)
|
|
||||||
if not connected_watches[watch_id] then return end
|
|
||||||
connected_watches[watch_id][hash_connector(host)] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local request_create_watch = queue(remotes.create_watch)
|
|
||||||
local request_remove_watch = queue(remotes.remove_watch)
|
|
||||||
local request_watch_data = queue(remotes.request_watch_data)
|
|
||||||
local request_stop_watch = queue(remotes.stop_watch)
|
|
||||||
local request_record_watch = queue(remotes.start_record_watch)
|
|
||||||
local request_connect_watch = queue(remotes.connect_watch)
|
|
||||||
local request_disconnect_watch = queue(remotes.disconnect_watch)
|
|
||||||
-- local set_lon_enabled = queue(remotes.set_lon_enabled)
|
|
||||||
|
|
||||||
return function()
|
|
||||||
|
|
||||||
for from, scheduler_id, system, watch_id in request_create_watch:iter() do
|
|
||||||
local scheduler = public[scheduler_id]
|
|
||||||
|
|
||||||
if scheduler.class_name ~= "Scheduler" then continue end
|
|
||||||
if scheduler == nil then continue end
|
|
||||||
|
|
||||||
create_watch_for_id(scheduler, system, watch_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
for from, watch_id in request_stop_watch:iter() do
|
|
||||||
stop_record_watch(watch_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
for from, watch_id in request_remove_watch:iter() do
|
|
||||||
remove_watch_id(watch_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
for from, watch_id, frame in request_watch_data:iter() do
|
|
||||||
send_watch_data_to(
|
|
||||||
reverse_connector(from),
|
|
||||||
watch_id,
|
|
||||||
frame
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
for from, watch_id in request_record_watch:iter() do
|
|
||||||
start_record_watch(watch_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
for from, watch_id in request_connect_watch:iter() do
|
|
||||||
connect_watch(reverse_connector(from), watch_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
for from, watch_id in request_disconnect_watch:iter() do
|
|
||||||
disconnect_watch(from, watch_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
for watch_id, hosts in connected_watches do
|
|
||||||
local watch = stored_watches[watch_id]
|
|
||||||
local current_frame = watch.watch.frame
|
|
||||||
local frame_data = watch.watch.frames[current_frame] or {types = {}}
|
|
||||||
local changes = #frame_data.types
|
|
||||||
|
|
||||||
for _, host in hosts do
|
|
||||||
remotes.update_overview:fire(host, watch_id, current_frame, changes)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
local types = require(script.Parent.Parent.modules.types)
|
|
||||||
local world_hook = require(script.Parent.world_hook)
|
|
||||||
|
|
||||||
local NIL = newproxy()
|
|
||||||
|
|
||||||
type ChangeTypes = "remove" | "clear" | "delete" | "add" | "set" | "entity" | "component"
|
|
||||||
type Changes = types.WatchLoggedChanges
|
|
||||||
|
|
||||||
export type SystemWatch = {
|
|
||||||
--- enables Lua Object Notation.
|
|
||||||
--- incurs a significant performance penalty.
|
|
||||||
enable_lon: boolean,
|
|
||||||
--- the current frame to process
|
|
||||||
frame: number,
|
|
||||||
|
|
||||||
frames: {[number]: Changes}
|
|
||||||
}
|
|
||||||
|
|
||||||
local function create_changes()
|
|
||||||
return {
|
|
||||||
types = {},
|
|
||||||
entities = {},
|
|
||||||
component = {},
|
|
||||||
values = {},
|
|
||||||
worlds = {}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local function step_watch(watch: SystemWatch)
|
|
||||||
watch.frame += 1
|
|
||||||
watch.frames[watch.frame] = create_changes()
|
|
||||||
end
|
|
||||||
|
|
||||||
local function track_watch(watch: SystemWatch)
|
|
||||||
|
|
||||||
local hooks = {
|
|
||||||
|
|
||||||
world_hook.hook_onto("remove", function(self, id, component)
|
|
||||||
local frame = watch.frames[watch.frame]
|
|
||||||
|
|
||||||
table.insert(frame.types, "remove")
|
|
||||||
table.insert(frame.entities, id)
|
|
||||||
table.insert(frame.component, component)
|
|
||||||
table.insert(frame.values, NIL)
|
|
||||||
table.insert(frame.worlds, self)
|
|
||||||
end),
|
|
||||||
|
|
||||||
world_hook.hook_onto("clear", function(self, id)
|
|
||||||
local frame = watch.frames[watch.frame]
|
|
||||||
|
|
||||||
table.insert(frame.types, "clear")
|
|
||||||
table.insert(frame.entities, id)
|
|
||||||
table.insert(frame.component, NIL)
|
|
||||||
table.insert(frame.values, NIL)
|
|
||||||
table.insert(frame.worlds, self)
|
|
||||||
end),
|
|
||||||
|
|
||||||
world_hook.hook_onto("delete", function(self, id)
|
|
||||||
local frame = watch.frames[watch.frame]
|
|
||||||
|
|
||||||
table.insert(frame.types, "delete")
|
|
||||||
table.insert(frame.entities, id)
|
|
||||||
table.insert(frame.component, NIL)
|
|
||||||
table.insert(frame.values, NIL)
|
|
||||||
table.insert(frame.worlds, self)
|
|
||||||
end),
|
|
||||||
|
|
||||||
world_hook.hook_onto("add", function(self, id, component)
|
|
||||||
local frame = watch.frames[watch.frame]
|
|
||||||
|
|
||||||
table.insert(frame.types, "add")
|
|
||||||
table.insert(frame.entities, id)
|
|
||||||
table.insert(frame.component, component)
|
|
||||||
table.insert(frame.values, NIL)
|
|
||||||
table.insert(frame.worlds, self)
|
|
||||||
end),
|
|
||||||
|
|
||||||
world_hook.hook_onto("set", function(self, entity, component, value)
|
|
||||||
if self:has(entity, component) then
|
|
||||||
local frame = watch.frames[watch.frame]
|
|
||||||
|
|
||||||
table.insert(frame.types, "change")
|
|
||||||
table.insert(frame.entities, entity)
|
|
||||||
table.insert(frame.component, component)
|
|
||||||
table.insert(frame.values, value)
|
|
||||||
table.insert(frame.worlds, self)
|
|
||||||
else
|
|
||||||
local frame = watch.frames[watch.frame]
|
|
||||||
|
|
||||||
table.insert(frame.types, "move")
|
|
||||||
table.insert(frame.entities, entity)
|
|
||||||
table.insert(frame.component, component)
|
|
||||||
table.insert(frame.values, value)
|
|
||||||
table.insert(frame.worlds, self)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
--- stops all hooks
|
|
||||||
local function stop_hook()
|
|
||||||
for _, destroy in hooks do
|
|
||||||
destroy()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return stop_hook
|
|
||||||
end
|
|
||||||
|
|
||||||
local function create_watch()
|
|
||||||
local watch: SystemWatch = {
|
|
||||||
enable_lon = false,
|
|
||||||
|
|
||||||
frame = 0,
|
|
||||||
frames = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return watch
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
|
|
||||||
create_watch = create_watch,
|
|
||||||
track_watch = track_watch,
|
|
||||||
step_watch = step_watch,
|
|
||||||
|
|
||||||
NIL = NIL
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
|
|
||||||
local public = require(script.Parent.public)
|
|
||||||
|
|
||||||
local function i_hook_onto(key: string, hooks: {(...any) -> ()})
|
|
||||||
|
|
||||||
local to_unhook = {}
|
|
||||||
for _, world_data in ipairs(public) do
|
|
||||||
local world = world_data.world
|
|
||||||
if not world then continue end
|
|
||||||
|
|
||||||
local method: any = world[key]
|
|
||||||
|
|
||||||
assert(typeof(method) == "function", "can only hook onto functions")
|
|
||||||
|
|
||||||
-- create a new wrapper function
|
|
||||||
local function run_hook(...)
|
|
||||||
for _, hook in hooks do
|
|
||||||
hook(...)
|
|
||||||
end
|
|
||||||
return method(...)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- print(debug.info(world[key], "s"))
|
|
||||||
world[key] = run_hook
|
|
||||||
to_unhook[world] = method
|
|
||||||
end
|
|
||||||
|
|
||||||
return function()
|
|
||||||
for world, method in to_unhook do
|
|
||||||
world[key] = method
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local hooks = {}
|
|
||||||
|
|
||||||
local function find_swap_pop<T>(list: {T}, value: T)
|
|
||||||
local idx = table.find(list, value)
|
|
||||||
if not idx then return end
|
|
||||||
list[idx] = list[#list]
|
|
||||||
list[#list] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local function hook_onto(key: string, hook: (...any) -> ())
|
|
||||||
|
|
||||||
if hooks[key] == nil then
|
|
||||||
local callbacks = {}
|
|
||||||
local cleanup = i_hook_onto(key, callbacks)
|
|
||||||
hooks[key] = {
|
|
||||||
cleanup = cleanup,
|
|
||||||
callbacks = callbacks
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local hook_info = hooks[key]
|
|
||||||
local dead = false
|
|
||||||
table.insert(hook_info.callbacks, hook)
|
|
||||||
|
|
||||||
local function unhook()
|
|
||||||
if dead then return end
|
|
||||||
dead = true
|
|
||||||
find_swap_pop(hook_info.callbacks, hook)
|
|
||||||
|
|
||||||
if hook_info.callbacks[1] == nil then
|
|
||||||
hook_info.cleanup()
|
|
||||||
hooks[key] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return unhook
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
hook_onto = hook_onto
|
|
||||||
}
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local theme = require(script.Parent.Parent.Parent.util.theme)
|
|
||||||
local container = require(script.Parent.Parent.util.container)
|
|
||||||
local padding = require(script.Parent.Parent.util.padding)
|
|
||||||
local typography = require(script.Parent.typography)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local source = vide.source
|
|
||||||
local changed = vide.changed
|
|
||||||
local spring = vide.spring
|
|
||||||
|
|
||||||
local CHEVRON_DOWN = "rbxassetid://10709790948"
|
|
||||||
local CHEVRON_UP = "rbxassetid://10709791523"
|
|
||||||
|
|
||||||
type can<T> = T | () -> T
|
|
||||||
type props = {
|
|
||||||
text: can<string>,
|
|
||||||
expanded: () -> boolean,
|
|
||||||
set_expanded: (boolean) -> (),
|
|
||||||
|
|
||||||
[any]: any
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
|
|
||||||
local gui_state = source(Enum.GuiState.Idle)
|
|
||||||
local container_size = source(Vector2.zero)
|
|
||||||
|
|
||||||
return container {
|
|
||||||
Name = "Accordion",
|
|
||||||
Size = spring(function()
|
|
||||||
if props.expanded() == false then return UDim2.new(1, 0, 0, 32) end
|
|
||||||
return UDim2.new(1, 0, 0, 40 + container_size().Y)
|
|
||||||
end, 0.1),
|
|
||||||
ClipsDescendants = true,
|
|
||||||
|
|
||||||
create "ImageButton" {
|
|
||||||
Name = "Accordion",
|
|
||||||
AutoLocalize = false,
|
|
||||||
Size = UDim2.new(1, 0, 0, 32),
|
|
||||||
|
|
||||||
BackgroundColor3 = spring(function()
|
|
||||||
return if gui_state() == Enum.GuiState.Press then
|
|
||||||
theme.bg[-1]()
|
|
||||||
elseif gui_state() == Enum.GuiState.Hover then
|
|
||||||
theme.bg[3]()
|
|
||||||
else
|
|
||||||
theme.bg[0]()
|
|
||||||
end, 0.1),
|
|
||||||
|
|
||||||
padding {x = UDim.new(0, 8)},
|
|
||||||
create "UIListLayout" {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
HorizontalFlex = Enum.UIFlexAlignment.SpaceBetween,
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
|
||||||
Padding = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UICorner" {
|
|
||||||
CornerRadius = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
|
|
||||||
container {
|
|
||||||
Size = UDim2.fromOffset(16, 16),
|
|
||||||
create "ImageLabel" {
|
|
||||||
Size = UDim2.fromOffset(16, 16),
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
AutoLocalize = false,
|
|
||||||
Image = CHEVRON_DOWN,
|
|
||||||
|
|
||||||
Rotation = spring(function()
|
|
||||||
return if props.expanded() then 180 else 0
|
|
||||||
end, 0.1)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
typography {
|
|
||||||
size = UDim2.fromScale(0, 1),
|
|
||||||
text = props.text,
|
|
||||||
truncate = Enum.TextTruncate.SplitWord,
|
|
||||||
xalignment = Enum.TextXAlignment.Left,
|
|
||||||
|
|
||||||
create "UIFlexItem" {
|
|
||||||
FlexMode = Enum.UIFlexMode.Fill
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
Activated = function()
|
|
||||||
props.set_expanded(not props.expanded())
|
|
||||||
end,
|
|
||||||
|
|
||||||
changed("GuiState", gui_state)
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
container {
|
|
||||||
Name = "Children",
|
|
||||||
|
|
||||||
Position = UDim2.fromOffset(0, 40),
|
|
||||||
AutomaticSize = Enum.AutomaticSize.None,
|
|
||||||
Size = function()
|
|
||||||
return UDim2.new(1, 0, 0, container_size().Y)
|
|
||||||
end,
|
|
||||||
BackgroundColor3 = theme.bg[3],
|
|
||||||
|
|
||||||
ClipsDescendants = true,
|
|
||||||
|
|
||||||
container {
|
|
||||||
Size = UDim2.fromScale(1, 0),
|
|
||||||
AutomaticSize = Enum.AutomaticSize.Y,
|
|
||||||
|
|
||||||
unpack(props),
|
|
||||||
|
|
||||||
changed("AbsoluteSize", container_size)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local theme = require(script.Parent.Parent.Parent.util.theme)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local read = vide.read
|
|
||||||
|
|
||||||
type can<T> = (() -> T) | T
|
|
||||||
|
|
||||||
type Background = {
|
|
||||||
position: can<UDim2>?,
|
|
||||||
size: can<UDim2>?,
|
|
||||||
anchorpoint: can<UDim2>?,
|
|
||||||
automaticsize: can<Enum.AutomaticSize>?,
|
|
||||||
|
|
||||||
layoutorder: can<number>?,
|
|
||||||
zindex: can<number>?,
|
|
||||||
|
|
||||||
depth: can<number>?,
|
|
||||||
accent: can<boolean>?,
|
|
||||||
|
|
||||||
[number]: any
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: Background)
|
|
||||||
|
|
||||||
return create "Frame" {
|
|
||||||
Position = props.position,
|
|
||||||
Size = props.size or UDim2.fromScale(1, 1),
|
|
||||||
AnchorPoint = props.anchorpoint,
|
|
||||||
AutomaticSize = props.automaticsize,
|
|
||||||
AutoLocalize = false,
|
|
||||||
|
|
||||||
LayoutOrder = props.layoutorder,
|
|
||||||
|
|
||||||
ZIndex = props.zindex,
|
|
||||||
|
|
||||||
BackgroundColor3 = function()
|
|
||||||
return
|
|
||||||
if read(props.accent) then theme.acc[read(props.depth) or 0]()
|
|
||||||
else theme.bg[read(props.depth) or 0]()
|
|
||||||
end,
|
|
||||||
|
|
||||||
unpack(props)
|
|
||||||
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local anim = require(script.Parent.Parent.Parent.util.anim)
|
|
||||||
local theme = require(script.Parent.Parent.Parent.util.theme)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local read = vide.read
|
|
||||||
|
|
||||||
type can<T> = (() -> T) | T
|
|
||||||
|
|
||||||
type Background = {
|
|
||||||
position: can<UDim2>?,
|
|
||||||
size: can<UDim2>?,
|
|
||||||
anchorpoint: can<UDim2>?,
|
|
||||||
automaticsize: can<Enum.AutomaticSize>?,
|
|
||||||
|
|
||||||
layoutorder: can<number>?,
|
|
||||||
zindex: can<number>?,
|
|
||||||
|
|
||||||
checked: can<boolean>,
|
|
||||||
|
|
||||||
[number]: any
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: Background)
|
|
||||||
|
|
||||||
return create "Frame" {
|
|
||||||
Position = props.position,
|
|
||||||
Size = props.size or UDim2.fromOffset(24, 24),
|
|
||||||
AnchorPoint = props.anchorpoint or Vector2.new(0.5, 0.5),
|
|
||||||
AutomaticSize = props.automaticsize,
|
|
||||||
AutoLocalize = false,
|
|
||||||
|
|
||||||
LayoutOrder = props.layoutorder,
|
|
||||||
|
|
||||||
ZIndex = props.zindex,
|
|
||||||
|
|
||||||
BackgroundColor3 = anim(function()
|
|
||||||
return if read(props.checked) then theme.acc[3]() else theme.bg[1]()
|
|
||||||
end),
|
|
||||||
|
|
||||||
create "UIStroke" {
|
|
||||||
Color = function()
|
|
||||||
return if read(props.checked) then theme.acc[0]() else theme.bg[-3]()
|
|
||||||
end
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UICorner" {
|
|
||||||
CornerRadius = UDim.new(0, 4)
|
|
||||||
},
|
|
||||||
|
|
||||||
create "ImageLabel" {
|
|
||||||
Size = UDim2.fromScale(1, 1),
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
Image = "rbxassetid://100188624502987",
|
|
||||||
ImageTransparency = anim(function()
|
|
||||||
return if read(props.checked) then 0 else 1
|
|
||||||
end)
|
|
||||||
},
|
|
||||||
|
|
||||||
unpack(props)
|
|
||||||
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local theme = require(script.Parent.Parent.Parent.util.theme)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local read = vide.read
|
|
||||||
|
|
||||||
type can<T> = T | () -> T
|
|
||||||
type props = {
|
|
||||||
thickness: can<number>?,
|
|
||||||
position: can<UDim2>?,
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
|
|
||||||
return create "Frame" {
|
|
||||||
BackgroundColor3 = theme.bg[-2],
|
|
||||||
Position = props.position,
|
|
||||||
AutoLocalize = false,
|
|
||||||
Size = function()
|
|
||||||
return UDim2.new(1, 0, 0, read(props.thickness) or 1)
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
@ -1,118 +0,0 @@
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local anim = require(script.Parent.Parent.Parent.util.anim)
|
|
||||||
local theme = require(script.Parent.Parent.Parent.util.theme)
|
|
||||||
local button = require(script.Parent.Parent.interactable.button)
|
|
||||||
local container = require(script.Parent.Parent.util.container)
|
|
||||||
local list = require(script.Parent.Parent.util.list)
|
|
||||||
local padding = require(script.Parent.Parent.util.padding)
|
|
||||||
local rounded_frame = require(script.Parent.Parent.util.rounded_frame)
|
|
||||||
local divider = require(script.Parent.divider)
|
|
||||||
local typography = require(script.Parent.typography)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local source = vide.source
|
|
||||||
local changed = vide.changed
|
|
||||||
local indexes = vide.indexes
|
|
||||||
local untrack = vide.untrack
|
|
||||||
local cleanup = vide.cleanup
|
|
||||||
|
|
||||||
type can<T> = T | () -> T
|
|
||||||
type props = {
|
|
||||||
labels: () -> {
|
|
||||||
{
|
|
||||||
title: string,
|
|
||||||
ui: () -> Instance | {Instance}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
|
|
||||||
local selected = source(1)
|
|
||||||
|
|
||||||
return list {
|
|
||||||
justifycontent = Enum.UIFlexAlignment.Fill,
|
|
||||||
spacing = UDim.new(),
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
Size = UDim2.new(1, 0, 0, 32),
|
|
||||||
AutoLocalize = false,
|
|
||||||
BackgroundColor3 = theme.bg[3],
|
|
||||||
|
|
||||||
divider {
|
|
||||||
position = UDim2.fromScale(0, 1),
|
|
||||||
},
|
|
||||||
|
|
||||||
container {
|
|
||||||
create "UIListLayout" {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal
|
|
||||||
},
|
|
||||||
|
|
||||||
indexes(props.labels, function(value, key)
|
|
||||||
local guistate = source(Enum.GuiState.Idle)
|
|
||||||
|
|
||||||
return rounded_frame {
|
|
||||||
name = key,
|
|
||||||
size = UDim2.fromOffset(50, 30),
|
|
||||||
automaticsize = Enum.AutomaticSize.X,
|
|
||||||
topleft = UDim.new(0, 4),
|
|
||||||
topright = UDim.new(0, 4),
|
|
||||||
|
|
||||||
color = function()
|
|
||||||
return if selected() == key then theme.bg[0]()
|
|
||||||
elseif guistate() == Enum.GuiState.Idle then theme.bg[3]()
|
|
||||||
else theme.bg[1]()
|
|
||||||
end,
|
|
||||||
|
|
||||||
create "TextButton" {
|
|
||||||
Size = UDim2.fromScale(1, 1),
|
|
||||||
AutoLocalize = false,
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
Activated = function()
|
|
||||||
selected(key)
|
|
||||||
end,
|
|
||||||
|
|
||||||
typography {
|
|
||||||
|
|
||||||
position = UDim2.fromScale(0.5, 0.5),
|
|
||||||
anchorpoint = Vector2.new(0.5, 0.5),
|
|
||||||
|
|
||||||
text = function()
|
|
||||||
return value().title
|
|
||||||
end,
|
|
||||||
|
|
||||||
textsize = 16
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
padding {
|
|
||||||
x = UDim.new(0, 24),
|
|
||||||
y = UDim.new(0, 2)
|
|
||||||
},
|
|
||||||
|
|
||||||
changed("GuiState", guistate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end),
|
|
||||||
},
|
|
||||||
|
|
||||||
ZIndex = 100,
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
Size = UDim2.new(1, 0, 1, 0),
|
|
||||||
AutoLocalize = false,
|
|
||||||
|
|
||||||
BackgroundColor3 = theme.bg[0],
|
|
||||||
|
|
||||||
function()
|
|
||||||
return untrack(props.labels()[selected()].ui)
|
|
||||||
end
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local theme = require(script.Parent.Parent.Parent.util.theme)
|
|
||||||
local list = require(script.Parent.Parent.util.list)
|
|
||||||
local padding = require(script.Parent.Parent.util.padding)
|
|
||||||
local background = require(script.Parent.background)
|
|
||||||
local typography = require(script.Parent.typography)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local read = vide.read
|
|
||||||
local show = vide.show
|
|
||||||
|
|
||||||
type can<T> = T | () -> T
|
|
||||||
type props = {
|
|
||||||
|
|
||||||
size: can<UDim2>?,
|
|
||||||
position: can<UDim2>?,
|
|
||||||
anchorpoint: can<UDim2>?,
|
|
||||||
layoutorder: can<number>?,
|
|
||||||
automaticsize: can<Enum.AutomaticSize>?,
|
|
||||||
|
|
||||||
name: can<string>?,
|
|
||||||
|
|
||||||
[number]: any
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
|
|
||||||
return create "Frame" {
|
|
||||||
Name = props.name,
|
|
||||||
Size = props.size or UDim2.fromScale(1, 0),
|
|
||||||
Position = props.position,
|
|
||||||
AnchorPoint = props.anchorpoint,
|
|
||||||
LayoutOrder = props.layoutorder,
|
|
||||||
AutomaticSize = props.automaticsize or Enum.AutomaticSize.Y,
|
|
||||||
BackgroundColor3 = theme.bg[0],
|
|
||||||
AutoLocalize = false,
|
|
||||||
|
|
||||||
create "UICorner" {
|
|
||||||
CornerRadius = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
create "UIStroke" {
|
|
||||||
Color = theme.bg[-3]
|
|
||||||
},
|
|
||||||
show(function()
|
|
||||||
return if read(props.name) then #read(props.name) > 0 else false
|
|
||||||
end, function()
|
|
||||||
return background {
|
|
||||||
size = UDim2.new(),
|
|
||||||
position = UDim2.fromOffset(4, -16),
|
|
||||||
automaticsize = Enum.AutomaticSize.XY,
|
|
||||||
|
|
||||||
typography {
|
|
||||||
text = props.name,
|
|
||||||
disabled = true,
|
|
||||||
|
|
||||||
textsize = 14
|
|
||||||
},
|
|
||||||
|
|
||||||
padding {x = UDim.new(0, 2), y = UDim.new()}
|
|
||||||
}
|
|
||||||
end),
|
|
||||||
|
|
||||||
padding {},
|
|
||||||
list {
|
|
||||||
unpack(props)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
@ -1,207 +0,0 @@
|
||||||
local RunService = game:GetService("RunService")
|
|
||||||
local UserInputService = game:GetService("UserInputService")
|
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local theme = require(script.Parent.Parent.Parent.util.theme)
|
|
||||||
local padding = require(script.Parent.Parent.util.padding)
|
|
||||||
local rounded_frame = require(script.Parent.Parent.util.rounded_frame)
|
|
||||||
local typography = require(script.Parent.typography)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local source = vide.source
|
|
||||||
local derive = vide.derive
|
|
||||||
local effect = vide.effect
|
|
||||||
local cleanup = vide.cleanup
|
|
||||||
local indexes = vide.indexes
|
|
||||||
local changed = vide.changed
|
|
||||||
local untrack = vide.untrack
|
|
||||||
|
|
||||||
local MAX_PIXELS_OFFSET = 32
|
|
||||||
|
|
||||||
local BEFORE = source(0)
|
|
||||||
local AFTER = source(1)
|
|
||||||
|
|
||||||
type ResizeableBar = {
|
|
||||||
meaning: () -> { string },
|
|
||||||
min_sizes: (() -> { vide.source<number>? })?,
|
|
||||||
sizes: vide.source<{ vide.source<number> }>,
|
|
||||||
suggested_sizes: { number }?,
|
|
||||||
|
|
||||||
splits: (vide.source<{ vide.source<number> }>)?,
|
|
||||||
base_splits: { number }?
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: ResizeableBar)
|
|
||||||
local meaning = props.meaning
|
|
||||||
local sizes = props.sizes
|
|
||||||
local min_sizes = props.min_sizes or source({}) :: never
|
|
||||||
local suggested_sizes = props.suggested_sizes or {}
|
|
||||||
|
|
||||||
local absolute_size = source(Vector2.one)
|
|
||||||
local absolute_position = source(Vector2.one)
|
|
||||||
|
|
||||||
local total = derive(function()
|
|
||||||
return #meaning()
|
|
||||||
end)
|
|
||||||
|
|
||||||
local splits = props.splits or source {}
|
|
||||||
local total_columns = derive(function()
|
|
||||||
return #props.meaning()
|
|
||||||
end)
|
|
||||||
|
|
||||||
effect(function(previous)
|
|
||||||
local new = {}
|
|
||||||
|
|
||||||
for i = 1, total_columns() - 1 do
|
|
||||||
local old_split = vide.read(previous and previous[i] or nil)
|
|
||||||
new[i] = source(math.min(if old_split and old_split ~= 1 then old_split else suggested_sizes[i] or 1, i / total_columns()))
|
|
||||||
end
|
|
||||||
|
|
||||||
splits(new)
|
|
||||||
return new
|
|
||||||
end)
|
|
||||||
|
|
||||||
for i, split in (props.base_splits :: never) or {} do
|
|
||||||
splits()[i](split)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_size(index: number)
|
|
||||||
local split_before = splits()[index - 1] or BEFORE :: never
|
|
||||||
local split_after = splits()[index] or AFTER :: never
|
|
||||||
|
|
||||||
local size = split_after() - split_before()
|
|
||||||
return size
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_min_size(i: number)
|
|
||||||
local min_size = min_sizes()[i]
|
|
||||||
return min_size and min_size() or 0.025
|
|
||||||
end
|
|
||||||
|
|
||||||
effect(function()
|
|
||||||
local new = setmetatable({}, {
|
|
||||||
__index = function()
|
|
||||||
return function() return 0 end
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
for i = 1, total() do
|
|
||||||
min_sizes()[i] = min_sizes()[i] or source(0.025)
|
|
||||||
untrack(function()
|
|
||||||
new[i] = derive(function()
|
|
||||||
return get_size(i)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
sizes(new :: any)
|
|
||||||
end)
|
|
||||||
|
|
||||||
local down = false
|
|
||||||
local updating = 0
|
|
||||||
|
|
||||||
return rounded_frame {
|
|
||||||
size = function()
|
|
||||||
return UDim2.new(1, 0, 0, 32)
|
|
||||||
end,
|
|
||||||
topleft = UDim.new(0, 8),
|
|
||||||
topright = UDim.new(0, 8),
|
|
||||||
color = theme.bg[1],
|
|
||||||
|
|
||||||
create "TextButton" {
|
|
||||||
Size = UDim2.fromScale(1, 1),
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
AutoLocalize = false,
|
|
||||||
Text = "",
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
Padding = UDim.new(0, 0)
|
|
||||||
},
|
|
||||||
|
|
||||||
indexes(meaning, function(column, i)
|
|
||||||
return typography {
|
|
||||||
size = function()
|
|
||||||
return UDim2.fromScale(get_size(i), 1)
|
|
||||||
end,
|
|
||||||
automaticsize = Enum.AutomaticSize.None,
|
|
||||||
text = function()
|
|
||||||
return column() or ""
|
|
||||||
end,
|
|
||||||
xalignment = Enum.TextXAlignment.Left,
|
|
||||||
truncate = Enum.TextTruncate.AtEnd,
|
|
||||||
header = true,
|
|
||||||
textsize = 18,
|
|
||||||
|
|
||||||
padding {x = UDim.new(0, 8)}
|
|
||||||
}
|
|
||||||
end),
|
|
||||||
|
|
||||||
changed("AbsoluteSize", absolute_size),
|
|
||||||
changed("AbsolutePosition", absolute_position),
|
|
||||||
|
|
||||||
MouseButton1Down = function(x: number)
|
|
||||||
-- find the nearest split
|
|
||||||
x -= absolute_position().X
|
|
||||||
local absolute_size = absolute_size()
|
|
||||||
local nearest = -1
|
|
||||||
for i, location in splits() do
|
|
||||||
local absolute_x = absolute_size.X * location()
|
|
||||||
if math.abs(x - absolute_x) > MAX_PIXELS_OFFSET then continue end
|
|
||||||
|
|
||||||
nearest = i
|
|
||||||
end
|
|
||||||
|
|
||||||
down = nearest ~= -1
|
|
||||||
updating = nearest
|
|
||||||
end,
|
|
||||||
|
|
||||||
MouseButton1Up = function()
|
|
||||||
down = false
|
|
||||||
end,
|
|
||||||
|
|
||||||
cleanup(RunService.Heartbeat:Connect(function()
|
|
||||||
local x = UserInputService:GetMouseLocation().X
|
|
||||||
|
|
||||||
x -= absolute_position().X
|
|
||||||
if down == false then return end
|
|
||||||
down = UserInputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) == true
|
|
||||||
|
|
||||||
local relative = x / absolute_size().X
|
|
||||||
local current = splits()[updating]()
|
|
||||||
local left_to_move = relative - current
|
|
||||||
|
|
||||||
if left_to_move > 0 then
|
|
||||||
for i = updating, total() - 1, 1 do
|
|
||||||
local min_size = get_min_size(i + 1)
|
|
||||||
local size = get_size(i + 1)
|
|
||||||
|
|
||||||
local new_size = math.max(size - left_to_move, min_size)
|
|
||||||
local difference = size - new_size
|
|
||||||
|
|
||||||
splits()[i](splits()[i]() + difference)
|
|
||||||
left_to_move -= difference
|
|
||||||
|
|
||||||
if left_to_move == 0 then break end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
for i = updating, 1, -1 do
|
|
||||||
local min_size = get_min_size(i)
|
|
||||||
|
|
||||||
local size = math.max(get_size(i), min_size) -- this is changing, which it isnt supposed to do
|
|
||||||
|
|
||||||
local new_size = math.max(size + left_to_move, min_size)
|
|
||||||
local difference = new_size - size
|
|
||||||
|
|
||||||
splits()[i](splits()[i]() + difference)
|
|
||||||
--assert((new_size + difference) == get_size(i - 1))
|
|
||||||
left_to_move -= difference
|
|
||||||
|
|
||||||
if left_to_move == 0 then break end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)),
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local theme = require(script.Parent.Parent.Parent.util.theme)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
|
|
||||||
type props = vide.vScrollingFrame
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
|
|
||||||
return create "ScrollingFrame" {
|
|
||||||
AutoLocalize = false,
|
|
||||||
ScrollBarThickness = 6,
|
|
||||||
ScrollBarImageColor3 = theme.fg_on_bg_low[0],
|
|
||||||
CanvasSize = UDim2.new(),
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
props
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
local UserInputService = game:GetService("UserInputService")
|
|
||||||
|
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local source = vide.source
|
|
||||||
local cleanup = vide.cleanup
|
|
||||||
local effect = vide.effect
|
|
||||||
local action = vide.action
|
|
||||||
local changed = vide.changed
|
|
||||||
local untrack = vide.untrack
|
|
||||||
local read = vide.read
|
|
||||||
|
|
||||||
type can<T> = T | () -> T
|
|
||||||
|
|
||||||
type snap_area = {
|
|
||||||
zindex: can<number>?,
|
|
||||||
snapped: (boolean) -> ()
|
|
||||||
}
|
|
||||||
|
|
||||||
type snappable = {
|
|
||||||
--- tells the object that you are dragging it
|
|
||||||
dragging: () -> boolean,
|
|
||||||
--- allows making the widget float by itself without being anchored to anything
|
|
||||||
allow_floating: can<boolean>,
|
|
||||||
|
|
||||||
--- callbacks that update the position and size
|
|
||||||
snapped: (boolean) -> (),
|
|
||||||
position: (UDim2) -> (),
|
|
||||||
size: (UDim2) -> ()
|
|
||||||
}
|
|
||||||
|
|
||||||
local function in_bounds(mpos: Vector2, pos: Vector2, size: Vector2)
|
|
||||||
return mpos.X >= pos.X and mpos.X <= pos.X + size.X and mpos.Y >= pos.Y and mpos.Y <= pos.Y + size.Y
|
|
||||||
end
|
|
||||||
|
|
||||||
return function()
|
|
||||||
|
|
||||||
local snap_areas = {}
|
|
||||||
local mouse_position = source(Vector2.zero)
|
|
||||||
|
|
||||||
local function snap_area(props: snap_area)
|
|
||||||
local position = source(Vector2.zero)
|
|
||||||
local size = source(Vector2.zero)
|
|
||||||
local docked = source(false)
|
|
||||||
|
|
||||||
return create "Frame" {
|
|
||||||
Name = "SnapArea",
|
|
||||||
AutoLocalize = false,
|
|
||||||
Size = UDim2.fromScale(1, 1),
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
changed("AbsoluteSize", size),
|
|
||||||
changed("AbsolutePosition", position),
|
|
||||||
|
|
||||||
action(function(ref)
|
|
||||||
snap_areas[ref] = {
|
|
||||||
position = position,
|
|
||||||
docked = docked,
|
|
||||||
size = size,
|
|
||||||
zindex = props.zindex or 0
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup(function()
|
|
||||||
snap_areas[ref] = nil
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local function snappable(props: snappable)
|
|
||||||
local snapped_to = source()
|
|
||||||
|
|
||||||
effect(function()
|
|
||||||
if props.dragging() == false then return end
|
|
||||||
local mpos = mouse_position()
|
|
||||||
|
|
||||||
untrack(function()
|
|
||||||
if snapped_to() then snapped_to().docked(false) end
|
|
||||||
|
|
||||||
local snap_to
|
|
||||||
|
|
||||||
for _, data in snap_areas do
|
|
||||||
if not in_bounds(mpos, data.position(), data.size()) then continue end
|
|
||||||
if snap_to and read(data.zindex) <= read(snap_to.zindex) then continue end
|
|
||||||
snap_to = data
|
|
||||||
end
|
|
||||||
|
|
||||||
if not snap_to and read(props.allow_floating) == false then return end
|
|
||||||
if snap_to and snap_to.docked() then return end
|
|
||||||
if snap_to then snap_to.docked(true) end
|
|
||||||
|
|
||||||
snapped_to(snap_to)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
effect(function()
|
|
||||||
props.snapped(if snapped_to() then true else false)
|
|
||||||
end)
|
|
||||||
|
|
||||||
effect(function()
|
|
||||||
if not snapped_to() then return end
|
|
||||||
local data = snapped_to()
|
|
||||||
local pos = data.position()
|
|
||||||
local size = data.size()
|
|
||||||
|
|
||||||
props.position(UDim2.fromOffset(pos.X, pos.Y))
|
|
||||||
props.size(UDim2.fromOffset(size.X, size.Y))
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
cleanup(UserInputService.InputChanged:Connect(function(input)
|
|
||||||
if input.UserInputType ~= Enum.UserInputType.MouseMovement then return end
|
|
||||||
mouse_position(Vector2.new(input.Position.X, input.Position.Y))
|
|
||||||
end))
|
|
||||||
|
|
||||||
return {
|
|
||||||
snap_area = snap_area,
|
|
||||||
snappable = snappable
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,179 +0,0 @@
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local theme = require(script.Parent.Parent.Parent.util.theme)
|
|
||||||
local button = require(script.Parent.Parent.interactable.button)
|
|
||||||
local rounded_frame = require(script.Parent.Parent.util.rounded_frame)
|
|
||||||
local virtualscroller = require(script.Parent.Parent.util.virtualscroller)
|
|
||||||
local resizeable_bar = require(script.Parent.resizeable_bar)
|
|
||||||
local scroll_frame = require(script.Parent.scroll_frame)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local source = vide.source
|
|
||||||
local derive = vide.derive
|
|
||||||
local indexes = vide.indexes
|
|
||||||
|
|
||||||
type can<T> = T | () -> T
|
|
||||||
type table = {
|
|
||||||
size: can<UDim2>?,
|
|
||||||
suggested_column_sizes: { number }?,
|
|
||||||
|
|
||||||
base_splits: { number }?,
|
|
||||||
columns: () -> {{any}},
|
|
||||||
|
|
||||||
on_click: (column: number, row: number) -> (),
|
|
||||||
on_click2: (column: number, row: number) -> (),
|
|
||||||
read_value: (column: number, row: number) -> string,
|
|
||||||
|
|
||||||
below: {[number]: any}?,
|
|
||||||
|
|
||||||
[number]: any
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: table)
|
|
||||||
local sizes = source({})
|
|
||||||
local splits = source({})
|
|
||||||
|
|
||||||
local meaning = derive(function()
|
|
||||||
local t = {}
|
|
||||||
|
|
||||||
for i, column in props.columns() do
|
|
||||||
t[i] = column[1]
|
|
||||||
end
|
|
||||||
|
|
||||||
return t
|
|
||||||
end)
|
|
||||||
|
|
||||||
local function get_size(index: number)
|
|
||||||
local split_before = splits()[index - 1] or source(0) :: never
|
|
||||||
local split_after = splits()[index] or source(1) :: never
|
|
||||||
|
|
||||||
local size = split_after() - split_before()
|
|
||||||
return size
|
|
||||||
end
|
|
||||||
|
|
||||||
return scroll_frame {
|
|
||||||
Size = props.size or UDim2.new(1, 0, 0, 8 * 32),
|
|
||||||
CanvasSize = function()
|
|
||||||
return UDim2.new(1, 0)
|
|
||||||
end,
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
VerticalFlex = Enum.UIFlexAlignment.SpaceEvenly
|
|
||||||
},
|
|
||||||
|
|
||||||
resizeable_bar {
|
|
||||||
meaning = meaning,
|
|
||||||
sizes = sizes,
|
|
||||||
splits = splits,
|
|
||||||
base_splits = props.base_splits,
|
|
||||||
suggested_sizes = props.suggested_column_sizes
|
|
||||||
},
|
|
||||||
|
|
||||||
create "Folder" {
|
|
||||||
|
|
||||||
indexes(meaning, function(_, i)
|
|
||||||
return create "Frame" {
|
|
||||||
Size = UDim2.new(0, 1, 1, -32),
|
|
||||||
AutoLocalize = false,
|
|
||||||
Position = function()
|
|
||||||
local pos = splits()[i]
|
|
||||||
return if not pos then UDim2.fromScale(0, 0) else UDim2.fromScale(pos(), 0)
|
|
||||||
end,
|
|
||||||
|
|
||||||
BackgroundColor3 = theme.bg[-1],
|
|
||||||
|
|
||||||
ZIndex = 100
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
},
|
|
||||||
|
|
||||||
virtualscroller {
|
|
||||||
size = UDim2.fromScale(1, 0),
|
|
||||||
|
|
||||||
create "UIFlexItem" {
|
|
||||||
FlexMode = Enum.UIFlexMode.Grow,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
BackgroundColor3 = theme.bg[0],
|
|
||||||
VerticalScrollBarInset = Enum.ScrollBarInset.None,
|
|
||||||
BackgroundTransparency = 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
item_size = 32,
|
|
||||||
item = function(index)
|
|
||||||
|
|
||||||
return create "Frame" {
|
|
||||||
Size = UDim2.new(1, 0, 0, 32),
|
|
||||||
AutoLocalize = false,
|
|
||||||
|
|
||||||
BackgroundColor3 = theme.bg[2],
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
Padding = UDim.new(0, 0)
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UIStroke" {
|
|
||||||
Color = theme.bg[-1],
|
|
||||||
},
|
|
||||||
|
|
||||||
indexes(props.columns, function(column, i)
|
|
||||||
return button {
|
|
||||||
size = function()
|
|
||||||
local column_size = get_size(i)
|
|
||||||
return UDim2.new(column_size, 0, 0, 32)
|
|
||||||
end,
|
|
||||||
|
|
||||||
text = function()
|
|
||||||
return props.read_value(i, index() + 1) or ""
|
|
||||||
end,
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center
|
|
||||||
},
|
|
||||||
|
|
||||||
xalignment = Enum.TextXAlignment.Left,
|
|
||||||
|
|
||||||
corner = false,
|
|
||||||
stroke = false,
|
|
||||||
code = true,
|
|
||||||
|
|
||||||
activated = function()
|
|
||||||
props.on_click(i, index() + 2)
|
|
||||||
end,
|
|
||||||
|
|
||||||
mouse2 = function()
|
|
||||||
props.on_click2(i, index() + 2)
|
|
||||||
end
|
|
||||||
} :: Instance
|
|
||||||
end)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
end,
|
|
||||||
|
|
||||||
max_items = function()
|
|
||||||
local value = (props.columns()[1] ~= nil and #props.columns()[1] or 0)
|
|
||||||
return value - 1
|
|
||||||
end,
|
|
||||||
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
rounded_frame {
|
|
||||||
size = function()
|
|
||||||
return UDim2.new(1, 0, 0, 32)
|
|
||||||
end,
|
|
||||||
|
|
||||||
color = theme.bg[1],
|
|
||||||
|
|
||||||
props.below,
|
|
||||||
|
|
||||||
bottomleft = UDim.new(0, 8),
|
|
||||||
bottomright = UDim.new(0, 8),
|
|
||||||
},
|
|
||||||
|
|
||||||
unpack(props)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local anim = require(script.Parent.Parent.Parent.util.anim)
|
|
||||||
local theme = require(script.Parent.Parent.Parent.util.theme)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local read = vide.read
|
|
||||||
|
|
||||||
type can<T> = T | () -> T
|
|
||||||
type props = {
|
|
||||||
|
|
||||||
size: can<UDim2>?,
|
|
||||||
position: can<UDim2>?,
|
|
||||||
anchorpoint: can<Vector2>?,
|
|
||||||
automaticsize: can<Enum.AutomaticSize>?,
|
|
||||||
|
|
||||||
accent: can<boolean>?,
|
|
||||||
|
|
||||||
xalignment: can<Enum.TextXAlignment>?,
|
|
||||||
yalignment: can<Enum.TextYAlignment>?,
|
|
||||||
truncate: can<Enum.TextTruncate>?,
|
|
||||||
wrapped: can<boolean>?,
|
|
||||||
|
|
||||||
header: can<boolean>?,
|
|
||||||
code: can<boolean>?,
|
|
||||||
disabled: can<boolean>?,
|
|
||||||
|
|
||||||
text: can<string>,
|
|
||||||
textsize: can<number>?,
|
|
||||||
|
|
||||||
visible: can<boolean>?,
|
|
||||||
|
|
||||||
[number]: any
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
|
|
||||||
local function font()
|
|
||||||
return if read(props.code) then theme.code else theme.font
|
|
||||||
end
|
|
||||||
|
|
||||||
local function fg()
|
|
||||||
local accent = read(props.accent)
|
|
||||||
local disabled = read(props.disabled)
|
|
||||||
|
|
||||||
return if accent then
|
|
||||||
if disabled then theme.fg_on_acc_low[0]()
|
|
||||||
else theme.fg_on_acc_high[0]()
|
|
||||||
else
|
|
||||||
if disabled then theme.fg_on_bg_low[0]()
|
|
||||||
else theme.fg_on_bg_high[0]()
|
|
||||||
end
|
|
||||||
|
|
||||||
return create "TextLabel" {
|
|
||||||
|
|
||||||
Size = props.size,
|
|
||||||
Position = props.position,
|
|
||||||
AnchorPoint = props.anchorpoint,
|
|
||||||
AutomaticSize = props.automaticsize or Enum.AutomaticSize.XY,
|
|
||||||
AutoLocalize = false,
|
|
||||||
|
|
||||||
TextXAlignment = props.xalignment,
|
|
||||||
TextYAlignment = props.yalignment,
|
|
||||||
TextTruncate = props.truncate,
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
Text = props.text,
|
|
||||||
|
|
||||||
TextSize = props.textsize or function()
|
|
||||||
return if read(props.header) then theme.header else theme.body
|
|
||||||
end,
|
|
||||||
TextWrapped = props.wrapped,
|
|
||||||
FontFace = function()
|
|
||||||
return if read(props.header) then
|
|
||||||
Font.new(font().Family, Enum.FontWeight.Bold)
|
|
||||||
else font()
|
|
||||||
end,
|
|
||||||
TextColor3 = anim(fg),
|
|
||||||
|
|
||||||
Visible = props.visible,
|
|
||||||
|
|
||||||
unpack(props)
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,229 +0,0 @@
|
||||||
local GuiService = game:GetService("GuiService")
|
|
||||||
local RunService = game:GetService("RunService")
|
|
||||||
local UserInputService = game:GetService("UserInputService")
|
|
||||||
|
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local theme = require(script.Parent.Parent.Parent.Parent.util.theme)
|
|
||||||
local container = require(script.Parent.Parent.Parent.util.container)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local source = vide.source
|
|
||||||
local spring = vide.spring
|
|
||||||
local changed = vide.changed
|
|
||||||
local cleanup = vide.cleanup
|
|
||||||
|
|
||||||
type Source<T> = vide.Source<T>
|
|
||||||
type props = {
|
|
||||||
|
|
||||||
resize_range: number,
|
|
||||||
min_size: Vector2,
|
|
||||||
|
|
||||||
can_resize_left: Source<boolean>,
|
|
||||||
can_resize_right: Source<boolean>,
|
|
||||||
can_resize_bottom: Source<boolean>,
|
|
||||||
can_resize_top: Source<boolean>,
|
|
||||||
|
|
||||||
resizing: () -> boolean,
|
|
||||||
}
|
|
||||||
|
|
||||||
local function xpos(s: () -> number)
|
|
||||||
return function()
|
|
||||||
return Vector2.new(s(), 0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function ypos(s: () -> number)
|
|
||||||
return function()
|
|
||||||
return Vector2.new(0, s())
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
local RESIZE_RANGE = props.resize_range
|
|
||||||
local MIN_SIZE = props.min_size
|
|
||||||
|
|
||||||
local can_resize_left = props.can_resize_left
|
|
||||||
local can_resize_right = props.can_resize_right
|
|
||||||
local can_resize_bottom = props.can_resize_bottom
|
|
||||||
local can_resize_top = props.can_resize_top
|
|
||||||
local resizing = props.resizing
|
|
||||||
|
|
||||||
local absolute_size = source(Vector2.new(1, 1))
|
|
||||||
local absolute_position = source(Vector2.zero)
|
|
||||||
|
|
||||||
local thickness = 4
|
|
||||||
local border_selected = theme.acc[8]
|
|
||||||
local gradient = NumberSequence.new({
|
|
||||||
NumberSequenceKeypoint.new(0, 1),
|
|
||||||
NumberSequenceKeypoint.new(0.25, 1),
|
|
||||||
NumberSequenceKeypoint.new(0.5, 0),
|
|
||||||
NumberSequenceKeypoint.new(0.75, 1),
|
|
||||||
NumberSequenceKeypoint.new(1, 1),
|
|
||||||
})
|
|
||||||
|
|
||||||
local x = source(0)
|
|
||||||
local y = source(0)
|
|
||||||
|
|
||||||
cleanup(RunService.Heartbeat:Connect(function()
|
|
||||||
local mposition = UserInputService:GetMouseLocation()
|
|
||||||
local top_inset, bottom_inset = GuiService:GetGuiInset()
|
|
||||||
mposition += - top_inset - bottom_inset
|
|
||||||
|
|
||||||
if MIN_SIZE.X ~= absolute_size().X or not resizing() then x(mposition.X) end
|
|
||||||
if MIN_SIZE.Y ~= absolute_size().Y or not resizing() then y(mposition.Y) end
|
|
||||||
end))
|
|
||||||
|
|
||||||
cleanup(RunService.RenderStepped:Connect(function()
|
|
||||||
local mposition = UserInputService:GetMouseLocation()
|
|
||||||
local top_inset, bottom_inset = GuiService:GetGuiInset()
|
|
||||||
mposition += - top_inset - bottom_inset
|
|
||||||
local x, y = mposition.X, mposition.Y
|
|
||||||
|
|
||||||
if resizing() then return end
|
|
||||||
|
|
||||||
local left = absolute_position().X
|
|
||||||
local top = absolute_position().Y
|
|
||||||
local right = left + absolute_size().X
|
|
||||||
local bottom = top + absolute_size().Y
|
|
||||||
|
|
||||||
local topleft = absolute_position() - Vector2.new(RESIZE_RANGE, RESIZE_RANGE)
|
|
||||||
local bottomright = absolute_position() + absolute_size() + Vector2.new(RESIZE_RANGE, RESIZE_RANGE)
|
|
||||||
|
|
||||||
-- perform AABB to check if the cursor is in range
|
|
||||||
local within_bounds = x > topleft.X and y > topleft.Y and x < bottomright.X and y < bottomright.Y
|
|
||||||
|
|
||||||
can_resize_top(y > top - RESIZE_RANGE and y < top and within_bounds)
|
|
||||||
can_resize_left(x < left + RESIZE_RANGE and x > left - RESIZE_RANGE and within_bounds)
|
|
||||||
can_resize_bottom(y < bottom + RESIZE_RANGE and y > bottom - RESIZE_RANGE and within_bounds)
|
|
||||||
can_resize_right(x < right + RESIZE_RANGE and x > right - RESIZE_RANGE and within_bounds)
|
|
||||||
end))
|
|
||||||
|
|
||||||
return {
|
|
||||||
|
|
||||||
changed("AbsoluteSize", function(value: Vector2)
|
|
||||||
if value.Magnitude == 0 then return end
|
|
||||||
absolute_size(value)
|
|
||||||
end),
|
|
||||||
changed("AbsolutePosition", absolute_position),
|
|
||||||
|
|
||||||
container {
|
|
||||||
|
|
||||||
Name = "Left",
|
|
||||||
|
|
||||||
Position = UDim2.fromScale(0, 0.5),
|
|
||||||
Size = UDim2.new(0, thickness, 1, thickness * 2),
|
|
||||||
AnchorPoint = Vector2.new(1, 0.5),
|
|
||||||
|
|
||||||
BackgroundColor3 = border_selected,
|
|
||||||
|
|
||||||
BackgroundTransparency = spring(function()
|
|
||||||
return can_resize_left() and 0 or 1
|
|
||||||
end, 0.2),
|
|
||||||
|
|
||||||
ZIndex = 1000,
|
|
||||||
|
|
||||||
create "UIGradient" {
|
|
||||||
|
|
||||||
Rotation = 90,
|
|
||||||
|
|
||||||
Transparency = gradient,
|
|
||||||
Offset = ypos(spring(function()
|
|
||||||
return (y() - absolute_position().Y - absolute_size().Y / 2) / absolute_size().Y
|
|
||||||
end, 0.1)),
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UICorner" {
|
|
||||||
CornerRadius = UDim.new(0, 4)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
container {
|
|
||||||
|
|
||||||
Name = "Right",
|
|
||||||
|
|
||||||
Position = UDim2.fromScale(1, 0.5),
|
|
||||||
Size = UDim2.new(0, thickness, 1, thickness * 2),
|
|
||||||
AnchorPoint = Vector2.new(0, 0.5),
|
|
||||||
|
|
||||||
BackgroundColor3 = border_selected,
|
|
||||||
|
|
||||||
BackgroundTransparency = spring(function()
|
|
||||||
return can_resize_right() and 0 or 1
|
|
||||||
end, 0.2),
|
|
||||||
|
|
||||||
ZIndex = 1000,
|
|
||||||
|
|
||||||
create "UIGradient" {
|
|
||||||
|
|
||||||
Rotation = 90,
|
|
||||||
|
|
||||||
Transparency = gradient,
|
|
||||||
Offset = ypos(spring(function()
|
|
||||||
return (y() - absolute_position().Y - absolute_size().Y / 2) / absolute_size().Y
|
|
||||||
end, 0.1)),
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UICorner" {
|
|
||||||
CornerRadius = UDim.new(0, 4)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
container {
|
|
||||||
|
|
||||||
Name = "Bottom",
|
|
||||||
|
|
||||||
Position = UDim2.fromScale(0.5, 1),
|
|
||||||
Size = UDim2.new(1, thickness * 2, 0, thickness),
|
|
||||||
AnchorPoint = Vector2.new(0.5, 0),
|
|
||||||
|
|
||||||
BackgroundColor3 = border_selected,
|
|
||||||
|
|
||||||
BackgroundTransparency = spring(function()
|
|
||||||
return can_resize_bottom() and 0 or 1
|
|
||||||
end, 0.2),
|
|
||||||
|
|
||||||
ZIndex = 1000,
|
|
||||||
|
|
||||||
create "UIGradient" {
|
|
||||||
|
|
||||||
Transparency = gradient,
|
|
||||||
Offset = xpos(spring(function()
|
|
||||||
return (x() - absolute_position().X - absolute_size().X / 2) / absolute_size().X
|
|
||||||
end, 0.1)),
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UICorner" {
|
|
||||||
CornerRadius = UDim.new(0, 4)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
container {
|
|
||||||
|
|
||||||
Name = "Top",
|
|
||||||
|
|
||||||
Position = UDim2.fromScale(0.5, 0),
|
|
||||||
Size = UDim2.new(1, thickness * 2, 0, thickness),
|
|
||||||
AnchorPoint = Vector2.new(0.5, 1),
|
|
||||||
|
|
||||||
BackgroundColor3 = border_selected,
|
|
||||||
|
|
||||||
BackgroundTransparency = spring(function()
|
|
||||||
return can_resize_top() and 0 or 1
|
|
||||||
end, 0.2),
|
|
||||||
|
|
||||||
ZIndex = 1000,
|
|
||||||
|
|
||||||
create "UIGradient" {
|
|
||||||
|
|
||||||
Transparency = gradient,
|
|
||||||
Offset = xpos(spring(function()
|
|
||||||
return (x() - absolute_position().X - absolute_size().X / 2) / absolute_size().X
|
|
||||||
end, 0.1)),
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UICorner" {
|
|
||||||
CornerRadius = UDim.new(0, 4)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} :: { any }
|
|
||||||
end
|
|
||||||
|
|
@ -1,299 +0,0 @@
|
||||||
local GuiService = game:GetService("GuiService")
|
|
||||||
local Players = game:GetService("Players")
|
|
||||||
local RunService = game:GetService("RunService")
|
|
||||||
local UserInputService = game:GetService("UserInputService")
|
|
||||||
|
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local theme = require(script.Parent.Parent.Parent.util.theme)
|
|
||||||
local container = require(script.Parent.Parent.util.container)
|
|
||||||
local padding = require(script.Parent.Parent.util.padding)
|
|
||||||
local shadow = require(script.Parent.Parent.util.shadow)
|
|
||||||
local divider = require(script.Parent.divider)
|
|
||||||
local snapping = require(script.Parent.snapping)
|
|
||||||
local borders = require(script.borders)
|
|
||||||
local topbar = require(script.topbar)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local source = vide.source
|
|
||||||
local cleanup = vide.cleanup
|
|
||||||
local changed = vide.changed
|
|
||||||
local reference = vide.action
|
|
||||||
local spring = vide.spring
|
|
||||||
|
|
||||||
type can<T> = T | () -> T
|
|
||||||
type props = {
|
|
||||||
|
|
||||||
title: can<string>,
|
|
||||||
subtitle: can<string>?,
|
|
||||||
min_size: Vector2?,
|
|
||||||
position: Vector2?,
|
|
||||||
size: Vector2?,
|
|
||||||
|
|
||||||
bind_to_close: (() -> ())?,
|
|
||||||
|
|
||||||
[number]: any
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
local HIGHEST_DISPLAY_ORDER = 100000
|
|
||||||
local RESIZE_RANGE = 6
|
|
||||||
|
|
||||||
local docks
|
|
||||||
|
|
||||||
vide.mount(function()
|
|
||||||
|
|
||||||
docks = snapping()
|
|
||||||
|
|
||||||
return create "ScreenGui" {
|
|
||||||
Name = "docks",
|
|
||||||
AutoLocalize = false,
|
|
||||||
|
|
||||||
-- create "Frame" {
|
|
||||||
-- Size = UDim2.new(1, 0, 0, 200),
|
|
||||||
|
|
||||||
-- BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
-- docks.snap_area {}
|
|
||||||
-- },
|
|
||||||
|
|
||||||
-- create "Frame" {
|
|
||||||
-- Size = UDim2.new(1, 0, 0, 200),
|
|
||||||
-- Position = UDim2.fromScale(0, 1),
|
|
||||||
-- AnchorPoint = Vector2.new(0, 1),
|
|
||||||
-- BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
-- docks.snap_area {}
|
|
||||||
-- },
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
Size = UDim2.new(0, 16, 1, 0),
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
AutoLocalize = false,
|
|
||||||
|
|
||||||
docks.snap_area {}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
end, Players.LocalPlayer.PlayerGui)
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
local min_size = Vector2.new(100, 100):Max(props.min_size or Vector2.zero)
|
|
||||||
local position = props.position or Vector2.new(32, 32)
|
|
||||||
local base_size = props.size or min_size * 1.5
|
|
||||||
|
|
||||||
local x_size = source(math.max(min_size.X, base_size.X))
|
|
||||||
local y_size = source(math.max(min_size.Y, base_size.Y))
|
|
||||||
local x_position = source(position.X)
|
|
||||||
local y_position = source(position.Y)
|
|
||||||
|
|
||||||
local offset = source(Vector2.zero)
|
|
||||||
local dragging = source(false)
|
|
||||||
local absolute_position = source(Vector2.zero)
|
|
||||||
local absolute_size = source(Vector2.zero)
|
|
||||||
|
|
||||||
local can_resize_top = source(false)
|
|
||||||
local can_resize_bottom = source(false)
|
|
||||||
local can_resize_right = source(false)
|
|
||||||
local can_resize_left = source(false)
|
|
||||||
local resizing = source(false)
|
|
||||||
local ref = source()
|
|
||||||
local display_order = source(HIGHEST_DISPLAY_ORDER + 1)
|
|
||||||
HIGHEST_DISPLAY_ORDER += 1
|
|
||||||
|
|
||||||
local mouse_inside = source(false)
|
|
||||||
|
|
||||||
local top: Vector2
|
|
||||||
local bottom: Vector2
|
|
||||||
|
|
||||||
cleanup(UserInputService.InputEnded:Connect(function(input)
|
|
||||||
if
|
|
||||||
input.UserInputType ~= Enum.UserInputType.MouseButton1
|
|
||||||
and input.UserInputType ~= Enum.UserInputType.Touch
|
|
||||||
then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
resizing(false)
|
|
||||||
dragging(false)
|
|
||||||
end))
|
|
||||||
|
|
||||||
cleanup(UserInputService.InputChanged:Connect(function(input: InputObject)
|
|
||||||
if input.UserInputType ~= Enum.UserInputType.MouseMovement then return end
|
|
||||||
if not resizing() then return end
|
|
||||||
|
|
||||||
local mposition = UserInputService:GetMouseLocation()
|
|
||||||
local top_inset, bottom_inset = GuiService:GetGuiInset()
|
|
||||||
mposition += - top_inset - bottom_inset
|
|
||||||
local x, y = mposition.X, mposition.Y
|
|
||||||
|
|
||||||
if can_resize_bottom() then y_size(math.max(y - top.Y, min_size.Y)) end
|
|
||||||
if can_resize_right() then x_size(math.max(x - top.X, min_size.X)) end
|
|
||||||
if can_resize_top() then
|
|
||||||
y_size(math.max(bottom.Y - y, min_size.Y))
|
|
||||||
y_position(math.min(y, bottom.Y - min_size.Y))
|
|
||||||
end
|
|
||||||
if can_resize_left() then
|
|
||||||
x_size(math.max(bottom.X - x, min_size.X))
|
|
||||||
x_position(math.min(x, bottom.X - min_size.X))
|
|
||||||
end
|
|
||||||
end))
|
|
||||||
|
|
||||||
cleanup(UserInputService.InputBegan:Connect(function(input)
|
|
||||||
if input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end
|
|
||||||
if not dragging() then resizing(true) end
|
|
||||||
|
|
||||||
top = absolute_position()
|
|
||||||
bottom = absolute_position() + absolute_size()
|
|
||||||
|
|
||||||
local player_gui
|
|
||||||
if Players.LocalPlayer and RunService:IsRunning() then
|
|
||||||
player_gui = Players.LocalPlayer:WaitForChild("PlayerGui") :: PlayerGui
|
|
||||||
elseif RunService:IsStudio() and RunService:IsRunning() then
|
|
||||||
player_gui = game:GetService("CoreGui") :: any
|
|
||||||
else
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local objects = player_gui:GetGuiObjectsAtPosition(input.Position.X, input.Position.Y)
|
|
||||||
if #objects == 0 then return end
|
|
||||||
if not objects[1]:IsDescendantOf(ref()) then return end
|
|
||||||
|
|
||||||
display_order(HIGHEST_DISPLAY_ORDER + 1)
|
|
||||||
HIGHEST_DISPLAY_ORDER += 1
|
|
||||||
end))
|
|
||||||
|
|
||||||
cleanup(UserInputService.InputChanged:Connect(function(input: InputObject)
|
|
||||||
if dragging() == false then return end
|
|
||||||
if not UserInputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) then
|
|
||||||
dragging(false)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local position = UserInputService:GetMouseLocation()
|
|
||||||
-- local top_inset, bottom_inset = GuiService:GetGuiInset()
|
|
||||||
-- position += - top_inset - bottom_inset
|
|
||||||
x_position(position.X + offset().X)
|
|
||||||
y_position(position.Y + offset().Y)
|
|
||||||
end))
|
|
||||||
|
|
||||||
local snapped = source(false)
|
|
||||||
local snap_size = source(UDim2.new())
|
|
||||||
local snap_pos = source(UDim2.new())
|
|
||||||
|
|
||||||
local function radius()
|
|
||||||
return if snapped() then UDim.new() else UDim.new(0, 6)
|
|
||||||
end
|
|
||||||
|
|
||||||
return create "ScreenGui" {
|
|
||||||
Name = props.title,
|
|
||||||
AutoLocalize = false,
|
|
||||||
DisplayOrder = display_order,
|
|
||||||
reference(ref),
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
AutoLocalize = false,
|
|
||||||
Position = function()
|
|
||||||
return if snapped() then snap_pos() else UDim2.fromOffset(x_position(), y_position())
|
|
||||||
end,
|
|
||||||
|
|
||||||
Size = function()
|
|
||||||
return if snapped() then UDim2.fromOffset(x_size() + 6, snap_size().Y.Offset) else UDim2.fromOffset(x_size() + 6, y_size() + 6)
|
|
||||||
end,
|
|
||||||
|
|
||||||
Active = true,
|
|
||||||
|
|
||||||
BackgroundColor3 = theme.bg[0],
|
|
||||||
|
|
||||||
MouseMoved = function()
|
|
||||||
if resizing() then return end
|
|
||||||
|
|
||||||
local mposition = UserInputService:GetMouseLocation()
|
|
||||||
local top_inset, bottom_inset = GuiService:GetGuiInset()
|
|
||||||
position += - top_inset - bottom_inset
|
|
||||||
local x, y = mposition.X, mposition.Y
|
|
||||||
x -= absolute_position().X
|
|
||||||
y -= absolute_position().Y
|
|
||||||
|
|
||||||
can_resize_top(y < RESIZE_RANGE)
|
|
||||||
can_resize_left(x < RESIZE_RANGE)
|
|
||||||
can_resize_bottom(y > (absolute_size().Y - RESIZE_RANGE))
|
|
||||||
can_resize_right(x > (absolute_size().X - RESIZE_RANGE))
|
|
||||||
end,
|
|
||||||
|
|
||||||
MouseEnter = function()
|
|
||||||
mouse_inside(true)
|
|
||||||
end,
|
|
||||||
|
|
||||||
MouseLeave = function()
|
|
||||||
if resizing() then return end
|
|
||||||
if RunService:IsRunning() == false then return end
|
|
||||||
|
|
||||||
mouse_inside(false)
|
|
||||||
end,
|
|
||||||
|
|
||||||
changed("AbsolutePosition", absolute_position),
|
|
||||||
changed("AbsoluteSize", absolute_size),
|
|
||||||
|
|
||||||
create "UICorner" {
|
|
||||||
CornerRadius = radius
|
|
||||||
},
|
|
||||||
|
|
||||||
shadow {},
|
|
||||||
|
|
||||||
borders {
|
|
||||||
|
|
||||||
resize_range = RESIZE_RANGE,
|
|
||||||
min_size = min_size,
|
|
||||||
|
|
||||||
can_resize_top = can_resize_top,
|
|
||||||
can_resize_bottom = can_resize_bottom,
|
|
||||||
can_resize_left = can_resize_left,
|
|
||||||
can_resize_right = can_resize_right,
|
|
||||||
|
|
||||||
resizing = resizing,
|
|
||||||
},
|
|
||||||
|
|
||||||
container {
|
|
||||||
Size = UDim2.fromScale(1, 1),
|
|
||||||
|
|
||||||
create "UIListLayout" {},
|
|
||||||
|
|
||||||
topbar {
|
|
||||||
title = props.title,
|
|
||||||
subtitle = props.subtitle,
|
|
||||||
dragging = dragging,
|
|
||||||
offset = offset,
|
|
||||||
bind_to_close = props.bind_to_close,
|
|
||||||
radius = radius
|
|
||||||
},
|
|
||||||
|
|
||||||
divider {},
|
|
||||||
|
|
||||||
container {
|
|
||||||
Size = UDim2.fromScale(1, 0),
|
|
||||||
|
|
||||||
padding {
|
|
||||||
x = UDim.new(0, 8),
|
|
||||||
y = UDim.new(0, 8)
|
|
||||||
},
|
|
||||||
|
|
||||||
unpack(props),
|
|
||||||
|
|
||||||
create "UIFlexItem" {
|
|
||||||
FlexMode = Enum.UIFlexMode.Grow
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
docks.snappable {
|
|
||||||
dragging = dragging,
|
|
||||||
|
|
||||||
snapped = snapped,
|
|
||||||
position = snap_pos,
|
|
||||||
size = snap_size
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,150 +0,0 @@
|
||||||
|
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local theme = require(script.Parent.Parent.Parent.Parent.util.theme)
|
|
||||||
local list = require(script.Parent.Parent.Parent.util.list)
|
|
||||||
local padding = require(script.Parent.Parent.Parent.util.padding)
|
|
||||||
local rounded_frame = require(script.Parent.Parent.Parent.util.rounded_frame)
|
|
||||||
local typography = require(script.Parent.Parent.typography)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local source = vide.source
|
|
||||||
local changed = vide.changed
|
|
||||||
local spring = vide.spring
|
|
||||||
local show = vide.show
|
|
||||||
|
|
||||||
type Source<T> = vide.Source<T>
|
|
||||||
type props = {
|
|
||||||
|
|
||||||
title: (string | () -> string)?,
|
|
||||||
subtitle: (string | () -> string)?,
|
|
||||||
bind_to_close: (() -> ())?,
|
|
||||||
|
|
||||||
radius: () -> UDim,
|
|
||||||
|
|
||||||
dragging: Source<boolean>,
|
|
||||||
offset: (new: Vector2) -> (),
|
|
||||||
|
|
||||||
[any]: any,
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
local bind_to_close = props.bind_to_close
|
|
||||||
local dragging = props.dragging
|
|
||||||
local offset = props.offset
|
|
||||||
|
|
||||||
local closeable = not not bind_to_close
|
|
||||||
|
|
||||||
local absolute_position = source(Vector2.zero)
|
|
||||||
|
|
||||||
local gui_state = source(Enum.GuiState.Idle)
|
|
||||||
|
|
||||||
return rounded_frame {
|
|
||||||
|
|
||||||
name = "Topbar",
|
|
||||||
|
|
||||||
size = UDim2.new(1, 0, 0, 48),
|
|
||||||
color = theme.bg[3],
|
|
||||||
|
|
||||||
topleft = props.radius,
|
|
||||||
topright = props.radius,
|
|
||||||
|
|
||||||
create "ImageButton" {
|
|
||||||
Size = UDim2.fromScale(1, 1),
|
|
||||||
AutoLocalize = false,
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
ZIndex = 1000,
|
|
||||||
|
|
||||||
changed("AbsolutePosition", absolute_position),
|
|
||||||
|
|
||||||
MouseButton1Down = function(x, y)
|
|
||||||
offset(absolute_position() - Vector2.new(x, y))
|
|
||||||
dragging(true)
|
|
||||||
end,
|
|
||||||
create "UIListLayout" {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
|
||||||
HorizontalFlex = Enum.UIFlexAlignment.Fill,
|
|
||||||
},
|
|
||||||
|
|
||||||
padding {
|
|
||||||
x = UDim.new(0, 16)
|
|
||||||
},
|
|
||||||
|
|
||||||
list {
|
|
||||||
spacing = UDim.new(),
|
|
||||||
|
|
||||||
typography {
|
|
||||||
size = UDim2.fromScale(1, 0),
|
|
||||||
text = props.title,
|
|
||||||
|
|
||||||
xalignment = Enum.TextXAlignment.Left,
|
|
||||||
truncate = Enum.TextTruncate.SplitWord,
|
|
||||||
textsize = 20,
|
|
||||||
header = true,
|
|
||||||
},
|
|
||||||
|
|
||||||
show(function()
|
|
||||||
return props.subtitle ~= nil
|
|
||||||
end, function()
|
|
||||||
return typography {
|
|
||||||
size = UDim2.fromScale(1, 0),
|
|
||||||
text = props.subtitle,
|
|
||||||
bold = true,
|
|
||||||
|
|
||||||
xalignment = Enum.TextXAlignment.Left,
|
|
||||||
truncate = Enum.TextTruncate.SplitWord,
|
|
||||||
|
|
||||||
textsize = 16,
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
show(source(closeable), function()
|
|
||||||
return create "ImageButton" {
|
|
||||||
|
|
||||||
Size = UDim2.fromOffset(32, 32),
|
|
||||||
|
|
||||||
BackgroundColor3 = spring(function()
|
|
||||||
return if gui_state() == Enum.GuiState.Hover then
|
|
||||||
theme.bg[5]()
|
|
||||||
elseif gui_state() == Enum.GuiState.Press then
|
|
||||||
theme.bg[0]()
|
|
||||||
else
|
|
||||||
theme.bg[3]()
|
|
||||||
end, 0.1),
|
|
||||||
|
|
||||||
changed("GuiState", gui_state),
|
|
||||||
|
|
||||||
Activated = props.bind_to_close,
|
|
||||||
|
|
||||||
create "UICorner" {
|
|
||||||
CornerRadius = UDim.new(1, 0)
|
|
||||||
},
|
|
||||||
|
|
||||||
create "ImageLabel" {
|
|
||||||
Size = UDim2.fromOffset(24, 24),
|
|
||||||
Position = UDim2.fromScale(0.5, 0.5),
|
|
||||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
||||||
AutoLocalize = false,
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
ImageColor3 = theme.fg_on_bg_high[3],
|
|
||||||
|
|
||||||
Image = "rbxassetid://10747384394",
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UIFlexItem" {
|
|
||||||
FlexMode = Enum.UIFlexMode.Custom,
|
|
||||||
ShrinkRatio = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local theme = require(script.Parent.Parent.Parent.util.theme)
|
|
||||||
local container = require(script.Parent.Parent.util.container)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local indexes = vide.indexes
|
|
||||||
local read = vide.read
|
|
||||||
|
|
||||||
type can<T> = T | () -> T
|
|
||||||
type props = {
|
|
||||||
|
|
||||||
position: can<UDim2>?,
|
|
||||||
size: can<UDim2>?,
|
|
||||||
anchorpoint: can<UDim2>?,
|
|
||||||
|
|
||||||
values: () -> {number},
|
|
||||||
max: can<number>?,
|
|
||||||
min: can<number>?,
|
|
||||||
|
|
||||||
[number]: Instance
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
|
|
||||||
local max = props.max or function()
|
|
||||||
return math.max(unpack(props.values()))
|
|
||||||
end
|
|
||||||
|
|
||||||
local function total()
|
|
||||||
return #props.values()
|
|
||||||
end
|
|
||||||
|
|
||||||
return container {
|
|
||||||
|
|
||||||
Position = props.position,
|
|
||||||
Size = props.size,
|
|
||||||
AnchorPoint = props.anchorpoint,
|
|
||||||
|
|
||||||
ClipsDescendants = true,
|
|
||||||
|
|
||||||
indexes(props.values, function(value, index)
|
|
||||||
|
|
||||||
return create "Frame" {
|
|
||||||
AutoLocalize = false,
|
|
||||||
|
|
||||||
Position = function()
|
|
||||||
return UDim2.fromScale((index - 1) / total(), 1)
|
|
||||||
end,
|
|
||||||
Size = function()
|
|
||||||
return UDim2.fromScale(
|
|
||||||
1/total(),
|
|
||||||
value() / read(max)
|
|
||||||
)
|
|
||||||
end,
|
|
||||||
AnchorPoint = Vector2.new(0, 1),
|
|
||||||
|
|
||||||
create "UIGradient" {
|
|
||||||
Color = function()
|
|
||||||
return ColorSequence.new(
|
|
||||||
theme.acc[10](),
|
|
||||||
theme.acc[-3]()
|
|
||||||
)
|
|
||||||
end,
|
|
||||||
|
|
||||||
Rotation = 90
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
end),
|
|
||||||
|
|
||||||
unpack(props)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local theme = require(script.Parent.Parent.Parent.util.theme)
|
|
||||||
local container = require(script.Parent.Parent.util.container)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local source = vide.source
|
|
||||||
local action = vide.action
|
|
||||||
local effect = vide.effect
|
|
||||||
|
|
||||||
type can<T> = T | () -> T
|
|
||||||
type props = {
|
|
||||||
|
|
||||||
position: can<UDim2>?,
|
|
||||||
size: can<UDim2>?,
|
|
||||||
anchorpoint: can<UDim2>?,
|
|
||||||
|
|
||||||
values: () -> {Path2DControlPoint},
|
|
||||||
|
|
||||||
[number]: Instance
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
|
|
||||||
local path2d: vide.Source<Path2D> = source()
|
|
||||||
|
|
||||||
effect(function()
|
|
||||||
if not path2d() then return end
|
|
||||||
|
|
||||||
local path = path2d()
|
|
||||||
path:SetControlPoints(props.values())
|
|
||||||
end)
|
|
||||||
|
|
||||||
return container {
|
|
||||||
|
|
||||||
Position = props.position,
|
|
||||||
Size = props.size,
|
|
||||||
AnchorPoint = props.anchorpoint,
|
|
||||||
|
|
||||||
create "Path2D" {
|
|
||||||
|
|
||||||
Thickness = 2,
|
|
||||||
Color3 = theme.acc[3],
|
|
||||||
|
|
||||||
action(path2d)
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
unpack(props)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local theme = require(script.Parent.Parent.Parent.util.theme)
|
|
||||||
local container = require(script.Parent.Parent.util.container)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local source = vide.source
|
|
||||||
local action = vide.action
|
|
||||||
local effect = vide.effect
|
|
||||||
local read = vide.read
|
|
||||||
|
|
||||||
type can<T> = T | () -> T
|
|
||||||
type props = {
|
|
||||||
|
|
||||||
position: can<UDim2>?,
|
|
||||||
size: can<UDim2>?,
|
|
||||||
anchorpoint: can<UDim2>?,
|
|
||||||
|
|
||||||
values: () -> {number},
|
|
||||||
max: can<number>?,
|
|
||||||
min: can<number>?,
|
|
||||||
|
|
||||||
[number]: Instance
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
|
|
||||||
local path2d: vide.Source<Path2D> = source()
|
|
||||||
|
|
||||||
effect(function()
|
|
||||||
if not path2d() then return end
|
|
||||||
|
|
||||||
local path = path2d()
|
|
||||||
local points = table.create(50)
|
|
||||||
local total = #props.values()
|
|
||||||
local max = read(props.max) or 100
|
|
||||||
local min = read(props.min) or 0
|
|
||||||
local diff = math.abs(max - min)
|
|
||||||
|
|
||||||
for index, value in props.values() do
|
|
||||||
table.insert(
|
|
||||||
points,
|
|
||||||
Path2DControlPoint.new(
|
|
||||||
UDim2.fromScale(
|
|
||||||
(index - 1) / (total - 1), 1 - (value - min) / diff
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
path:SetControlPoints(points)
|
|
||||||
end)
|
|
||||||
|
|
||||||
return container {
|
|
||||||
|
|
||||||
Position = props.position,
|
|
||||||
Size = props.size,
|
|
||||||
AnchorPoint = props.anchorpoint,
|
|
||||||
|
|
||||||
ClipsDescendants = true,
|
|
||||||
|
|
||||||
create "Path2D" {
|
|
||||||
|
|
||||||
Thickness = 2,
|
|
||||||
Color3 = theme.acc[3],
|
|
||||||
|
|
||||||
action(path2d)
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
unpack(props)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,160 +0,0 @@
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local anim = require(script.Parent.Parent.Parent.util.anim)
|
|
||||||
local theme = require(script.Parent.Parent.Parent.util.theme)
|
|
||||||
local typography = require(script.Parent.Parent.display.typography)
|
|
||||||
local padding = require(script.Parent.Parent.util.padding)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local source = vide.source
|
|
||||||
local changed = vide.changed
|
|
||||||
local show = vide.show
|
|
||||||
local read = vide.read
|
|
||||||
|
|
||||||
type can<T> = T | () -> T
|
|
||||||
type props = {
|
|
||||||
|
|
||||||
size: can<UDim2>?,
|
|
||||||
position: can<UDim2>?,
|
|
||||||
anchorpoint: can<Vector2>?,
|
|
||||||
automaticsize: can<Enum.AutomaticSize>?,
|
|
||||||
|
|
||||||
text: can<string>?,
|
|
||||||
disabled: can<boolean>?,
|
|
||||||
|
|
||||||
activated: () -> ()?,
|
|
||||||
mouse2: () -> ()?,
|
|
||||||
down: () -> ()?,
|
|
||||||
up: () -> ()?,
|
|
||||||
|
|
||||||
--- enables the stroke (enabled by default)
|
|
||||||
stroke: can<boolean>?,
|
|
||||||
--- enables the corner (enabled by default)
|
|
||||||
corner: can<boolean>?,
|
|
||||||
accent: can<boolean>?,
|
|
||||||
|
|
||||||
xalignment: can<Enum.TextXAlignment>?,
|
|
||||||
|
|
||||||
code: can<boolean>?,
|
|
||||||
|
|
||||||
[number]: any
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
|
|
||||||
local guistate = source(Enum.GuiState.Idle)
|
|
||||||
|
|
||||||
local function bg()
|
|
||||||
local accent = read(props.accent)
|
|
||||||
local guistate = guistate()
|
|
||||||
|
|
||||||
return if accent then
|
|
||||||
if guistate == Enum.GuiState.NonInteractable then theme.acc[-5]()
|
|
||||||
elseif guistate == Enum.GuiState.Idle then theme.acc[0]()
|
|
||||||
elseif guistate == Enum.GuiState.Hover then theme.acc[3]()
|
|
||||||
elseif guistate == Enum.GuiState.Press then theme.acc[-8]()
|
|
||||||
else theme.acc[0]()
|
|
||||||
else
|
|
||||||
if guistate == Enum.GuiState.NonInteractable then theme.bg[-2]()
|
|
||||||
elseif guistate == Enum.GuiState.Idle then theme.bg[3]()
|
|
||||||
elseif guistate == Enum.GuiState.Hover then theme.bg[6]()
|
|
||||||
elseif guistate == Enum.GuiState.Press then theme.bg[0]()
|
|
||||||
else theme.acc[0]()
|
|
||||||
end
|
|
||||||
|
|
||||||
local function stroke()
|
|
||||||
local accent = read(props.accent)
|
|
||||||
local guistate = guistate()
|
|
||||||
|
|
||||||
return if accent then
|
|
||||||
if guistate == Enum.GuiState.NonInteractable then theme.acc[-7]()
|
|
||||||
else theme.acc[-7]()
|
|
||||||
else
|
|
||||||
if guistate == Enum.GuiState.NonInteractable then theme.bg[-3]()
|
|
||||||
else theme.bg[-3]()
|
|
||||||
end
|
|
||||||
|
|
||||||
return create "TextButton" {
|
|
||||||
|
|
||||||
Name = props.text,
|
|
||||||
AutoLocalize = false,
|
|
||||||
|
|
||||||
Size = props.size or UDim2.fromOffset(100, 30),
|
|
||||||
Position = props.position,
|
|
||||||
AnchorPoint = props.anchorpoint,
|
|
||||||
AutomaticSize = props.automaticsize,
|
|
||||||
|
|
||||||
Interactable = function()
|
|
||||||
return not read(props.disabled)
|
|
||||||
end,
|
|
||||||
|
|
||||||
BackgroundColor3 = anim(bg),
|
|
||||||
|
|
||||||
Activated = props.activated,
|
|
||||||
MouseButton2Click = props.mouse2,
|
|
||||||
MouseButton1Down = props.down,
|
|
||||||
MouseButton1Up = props.up,
|
|
||||||
|
|
||||||
typography {
|
|
||||||
|
|
||||||
position = UDim2.fromScale(0.5, 0.5),
|
|
||||||
anchorpoint = Vector2.new(0.5, 0.5),
|
|
||||||
size = UDim2.fromScale(1, 1),
|
|
||||||
automaticsize = Enum.AutomaticSize.Y,
|
|
||||||
|
|
||||||
text = props.text,
|
|
||||||
truncate = Enum.TextTruncate.SplitWord,
|
|
||||||
xalignment = props.xalignment,
|
|
||||||
|
|
||||||
accent = props.accent,
|
|
||||||
disabled = props.disabled,
|
|
||||||
|
|
||||||
visible = function()
|
|
||||||
return read(props.text) ~= ""
|
|
||||||
end,
|
|
||||||
|
|
||||||
code = props.code,
|
|
||||||
|
|
||||||
create "UIFlexItem" {
|
|
||||||
FlexMode = Enum.UIFlexMode.Fill
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
show(
|
|
||||||
function()
|
|
||||||
return read(props.stroke) ~= false
|
|
||||||
end,
|
|
||||||
source(
|
|
||||||
create "UIStroke" {
|
|
||||||
ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
|
|
||||||
|
|
||||||
Color = anim(stroke),
|
|
||||||
Thickness = 1,
|
|
||||||
Enabled = props.stroke
|
|
||||||
}
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
show(
|
|
||||||
function()
|
|
||||||
return read(props.corner) ~= false
|
|
||||||
end,
|
|
||||||
source(
|
|
||||||
create "UICorner" {
|
|
||||||
CornerRadius = UDim.new(0,4)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
padding {
|
|
||||||
x = UDim.new(0, 8),
|
|
||||||
y = UDim.new(0, 2)
|
|
||||||
},
|
|
||||||
|
|
||||||
changed("GuiState", guistate),
|
|
||||||
|
|
||||||
unpack(props),
|
|
||||||
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
@ -1,166 +0,0 @@
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local theme = require(script.Parent.Parent.Parent.util.theme)
|
|
||||||
local scroll_frame = require(script.Parent.Parent.display.scroll_frame)
|
|
||||||
local container = require(script.Parent.Parent.util.container)
|
|
||||||
local list = require(script.Parent.Parent.util.list)
|
|
||||||
local padding = require(script.Parent.Parent.util.padding)
|
|
||||||
local portal = require(script.Parent.Parent.util.portal)
|
|
||||||
local button = require(script.Parent.button)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local source = vide.source
|
|
||||||
local changed = vide.changed
|
|
||||||
local indexes = vide.indexes
|
|
||||||
local spring = vide.spring
|
|
||||||
local read = vide.read
|
|
||||||
|
|
||||||
local MAX_SIZE = 100
|
|
||||||
|
|
||||||
type can<T> = T | () -> T
|
|
||||||
type dropdown = {
|
|
||||||
size: can<UDim2>?,
|
|
||||||
position: can<UDim2>?,
|
|
||||||
anchorpoint: can<Vector2>?,
|
|
||||||
|
|
||||||
selected: can<number>,
|
|
||||||
update_selected: (() -> number)?,
|
|
||||||
|
|
||||||
options: can<{string}>
|
|
||||||
}
|
|
||||||
|
|
||||||
local function dropdown(props: dropdown)
|
|
||||||
|
|
||||||
local selected = props.selected
|
|
||||||
local update_selected = props.update_selected or function() end
|
|
||||||
local options = props.options
|
|
||||||
|
|
||||||
local enabled = source(false)
|
|
||||||
local absolute_size = source(Vector2.zero)
|
|
||||||
|
|
||||||
local size = spring(function()
|
|
||||||
if not enabled() then return UDim2.fromScale(1, 0) end
|
|
||||||
return UDim2.new(1, 0, 0, math.min(MAX_SIZE, absolute_size().Y))
|
|
||||||
end, 0.1)
|
|
||||||
|
|
||||||
return button {
|
|
||||||
|
|
||||||
size = props.size or UDim2.fromOffset(200, 32),
|
|
||||||
position = props.position or UDim2.fromScale(0.5, 0.5),
|
|
||||||
anchorpoint = props.anchorpoint or Vector2.new(0.5, 0.5),
|
|
||||||
|
|
||||||
xalignment = Enum.TextXAlignment.Left,
|
|
||||||
|
|
||||||
text = function()
|
|
||||||
return read(options)[read(selected)]
|
|
||||||
end,
|
|
||||||
|
|
||||||
activated = function()
|
|
||||||
enabled(not enabled())
|
|
||||||
end,
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
|
||||||
Padding = UDim.new(0, 8),
|
|
||||||
},
|
|
||||||
|
|
||||||
container {
|
|
||||||
|
|
||||||
AnchorPoint = Vector2.new(1, 0),
|
|
||||||
Position = UDim2.fromScale(1, 0),
|
|
||||||
Size = UDim2.new(0, 18, 0, 16),
|
|
||||||
|
|
||||||
LayoutOrder = -1,
|
|
||||||
|
|
||||||
create "ImageLabel" {
|
|
||||||
|
|
||||||
Name = "arrow",
|
|
||||||
AutoLocalize = false,
|
|
||||||
|
|
||||||
Size = UDim2.new(0, 8, 0, 4),
|
|
||||||
Position = UDim2.fromScale(0.5, 0.5),
|
|
||||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
||||||
Rotation = spring(function()
|
|
||||||
return if enabled() then -180 else 0
|
|
||||||
end, 0.1),
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
BackgroundColor3 = theme.fg_on_bg_high[3],
|
|
||||||
|
|
||||||
Image = "rbxassetid://7260137654",
|
|
||||||
ImageColor3 = theme.fg_on_bg_low[3],
|
|
||||||
ScaleType = Enum.ScaleType.Stretch
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
portal {
|
|
||||||
inherit_layout = true,
|
|
||||||
|
|
||||||
container {
|
|
||||||
Position = UDim2.new(0, 1, 1, 4),
|
|
||||||
Size = size,
|
|
||||||
|
|
||||||
BackgroundTransparency = 0,
|
|
||||||
BackgroundColor3 = theme.bg[3],
|
|
||||||
|
|
||||||
ClipsDescendants = true,
|
|
||||||
|
|
||||||
Visible = function()
|
|
||||||
return size().Y.Offset > 1
|
|
||||||
end,
|
|
||||||
|
|
||||||
padding {
|
|
||||||
padding = UDim.new(0, 2)
|
|
||||||
},
|
|
||||||
|
|
||||||
scroll_frame {
|
|
||||||
Size = UDim2.fromScale(1, 1),
|
|
||||||
ScrollBarThickness = 4,
|
|
||||||
AutomaticCanvasSize = Enum.AutomaticSize.Y,
|
|
||||||
|
|
||||||
list {
|
|
||||||
spacing = UDim.new(0, 1),
|
|
||||||
|
|
||||||
changed("AbsoluteSize", absolute_size),
|
|
||||||
|
|
||||||
indexes(function()
|
|
||||||
return read(options)
|
|
||||||
end, function(value, key)
|
|
||||||
return button {
|
|
||||||
size = UDim2.new(1, 0, 0, 30),
|
|
||||||
text = value,
|
|
||||||
stroke = false,
|
|
||||||
|
|
||||||
activated = function()
|
|
||||||
enabled(false)
|
|
||||||
update_selected(key)
|
|
||||||
end,
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
|
||||||
Padding = UDim.new(0, 8),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UIStroke" {
|
|
||||||
Color = theme.bg[-3]
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UICorner" {
|
|
||||||
CornerRadius = UDim.new(0, 3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
return dropdown
|
|
||||||
|
|
@ -1,175 +0,0 @@
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local anim = require(script.Parent.Parent.Parent.util.anim)
|
|
||||||
local theme = require(script.Parent.Parent.Parent.util.theme)
|
|
||||||
local typography = require(script.Parent.Parent.display.typography)
|
|
||||||
local padding = require(script.Parent.Parent.util.padding)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local source = vide.source
|
|
||||||
local changed = vide.changed
|
|
||||||
local effect = vide.effect
|
|
||||||
local action = vide.action
|
|
||||||
local read = vide.read
|
|
||||||
|
|
||||||
type can<T> = T | () -> T
|
|
||||||
type props = {
|
|
||||||
|
|
||||||
size: can<UDim2>?,
|
|
||||||
position: can<UDim2>?,
|
|
||||||
anchorpoint: can<UDim2>?,
|
|
||||||
|
|
||||||
text: can<string>?,
|
|
||||||
placeholder: can<string>?,
|
|
||||||
|
|
||||||
multiline: can<boolean>?,
|
|
||||||
code: can<boolean>?,
|
|
||||||
|
|
||||||
disabled: can<boolean>?,
|
|
||||||
|
|
||||||
stroke: can<boolean>?,
|
|
||||||
corner: can<boolean>?,
|
|
||||||
|
|
||||||
--- called whenever a character is added / removed
|
|
||||||
oninput: ((new: string) -> ())?,
|
|
||||||
--- called whenever focus is lost
|
|
||||||
focuslost: ((text: string, enter: boolean?) -> ())?,
|
|
||||||
--- called whenever focus is lost by pressing enter
|
|
||||||
enter: ((text: string) -> ())?,
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
|
|
||||||
local guistate = source(Enum.GuiState.Idle)
|
|
||||||
local focused = source(false)
|
|
||||||
local textbox = source() :: vide.Source<TextBox>
|
|
||||||
local text = source("")
|
|
||||||
|
|
||||||
effect(function()
|
|
||||||
text(read(props.text) or "")
|
|
||||||
end)
|
|
||||||
|
|
||||||
local function bg()
|
|
||||||
local guistate = guistate()
|
|
||||||
|
|
||||||
return if guistate == Enum.GuiState.NonInteractable then theme.bg[0]()
|
|
||||||
elseif focused() then theme.bg[-3]()
|
|
||||||
else theme.bg[-2]()
|
|
||||||
end
|
|
||||||
|
|
||||||
local function fg()
|
|
||||||
local disabled = read(props.disabled)
|
|
||||||
|
|
||||||
return if disabled then theme.fg_on_bg_low[0]()
|
|
||||||
else theme.fg_on_bg_high[0]()
|
|
||||||
end
|
|
||||||
|
|
||||||
local function stroke()
|
|
||||||
local guistate = guistate()
|
|
||||||
|
|
||||||
return if guistate == Enum.GuiState.NonInteractable then theme.bg[-3]()
|
|
||||||
elseif focused() then theme.acc[5]()
|
|
||||||
elseif guistate == Enum.GuiState.Idle then theme.bg[-3]()
|
|
||||||
elseif guistate == Enum.GuiState.Hover then theme.bg[3]()
|
|
||||||
else theme.bg[-3]()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- this effect will automatically focus the textbox if focused is true
|
|
||||||
effect(function()
|
|
||||||
if focused() == true and textbox() then
|
|
||||||
textbox():CaptureFocus()
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
return create "TextButton" {
|
|
||||||
|
|
||||||
Name = props.placeholder or "Textbox",
|
|
||||||
AutoLocalize = false,
|
|
||||||
|
|
||||||
Size = props.size or UDim2.fromOffset(300, 30),
|
|
||||||
Position = props.position,
|
|
||||||
AnchorPoint = props.anchorpoint,
|
|
||||||
|
|
||||||
Activated = function()
|
|
||||||
focused(true)
|
|
||||||
end,
|
|
||||||
|
|
||||||
Interactable = function()
|
|
||||||
return not props.disabled
|
|
||||||
end,
|
|
||||||
|
|
||||||
BackgroundColor3 = anim(bg),
|
|
||||||
|
|
||||||
ClipsDescendants = true,
|
|
||||||
|
|
||||||
create "TextBox" {
|
|
||||||
|
|
||||||
Size = UDim2.fromScale(1, 1),
|
|
||||||
AutoLocalize = false,
|
|
||||||
|
|
||||||
MultiLine = props.multiline,
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
Focused = function()
|
|
||||||
focused(true)
|
|
||||||
end,
|
|
||||||
|
|
||||||
FocusLost = function(enter)
|
|
||||||
focused(false)
|
|
||||||
if props.focuslost then
|
|
||||||
props.focuslost(text(), enter)
|
|
||||||
end
|
|
||||||
|
|
||||||
if props.enter then
|
|
||||||
props.enter(text())
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
TextSize = theme.body,
|
|
||||||
FontFace = function()
|
|
||||||
return if read(props.code) then theme.code else theme.font
|
|
||||||
end,
|
|
||||||
TextColor3 = anim(fg),
|
|
||||||
PlaceholderColor3 = theme.fg_on_bg_low[0],
|
|
||||||
|
|
||||||
PlaceholderText = props.placeholder,
|
|
||||||
Text = props.text,
|
|
||||||
|
|
||||||
ClipsDescendants = true,
|
|
||||||
|
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
|
||||||
TextYAlignment = function()
|
|
||||||
return if read(props.multiline) then Enum.TextYAlignment.Top else Enum.TextYAlignment.Center
|
|
||||||
end,
|
|
||||||
|
|
||||||
action(textbox),
|
|
||||||
changed("Text", text),
|
|
||||||
if props.oninput then changed("Text", props.oninput) else nil
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UIStroke" {
|
|
||||||
ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
|
|
||||||
|
|
||||||
Color = anim(stroke),
|
|
||||||
Thickness = 1,
|
|
||||||
Enabled = props.stroke
|
|
||||||
},
|
|
||||||
|
|
||||||
create "UICorner" {
|
|
||||||
CornerRadius = function()
|
|
||||||
return if read(props.corner) == false then UDim.new() else UDim.new(0, 4)
|
|
||||||
end
|
|
||||||
},
|
|
||||||
|
|
||||||
padding {
|
|
||||||
x = UDim.new(0, 8),
|
|
||||||
y = UDim.new(0, 2)
|
|
||||||
},
|
|
||||||
|
|
||||||
changed("GuiState", guistate)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
--[[
|
|
||||||
|
|
||||||
container is a basic transparent frame that covers the entire frame.
|
|
||||||
|
|
||||||
]]
|
|
||||||
|
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
|
|
||||||
local function container(props: vide.vFrame)
|
|
||||||
return create "Frame" {
|
|
||||||
|
|
||||||
Name = "Container",
|
|
||||||
AutoLocalize = false,
|
|
||||||
|
|
||||||
Size = UDim2.fromScale(1, 1),
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
props,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
return container
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local container = require(script.Parent.container)
|
|
||||||
|
|
||||||
local read = vide.read
|
|
||||||
|
|
||||||
type can<T> = T | () -> T
|
|
||||||
type props = {
|
|
||||||
|
|
||||||
gap: can<number>,
|
|
||||||
direction: can<"x" | "y">?,
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
|
|
||||||
local function direction()
|
|
||||||
return read(props.direction) or "x"
|
|
||||||
end
|
|
||||||
|
|
||||||
return container {
|
|
||||||
|
|
||||||
Size = function()
|
|
||||||
return if direction() == "x" then
|
|
||||||
UDim2.new(0, read(props.gap), 1, 0)
|
|
||||||
else
|
|
||||||
UDim2.new(1, 0, 0, read(props.gap))
|
|
||||||
end
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
--[[
|
|
||||||
|
|
||||||
Creates a container for a list of elements.
|
|
||||||
|
|
||||||
]]
|
|
||||||
|
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local container = require(script.Parent.container)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local read = vide.read
|
|
||||||
|
|
||||||
type can<T> = (() -> T) | T
|
|
||||||
type layout = {
|
|
||||||
|
|
||||||
justifycontent: can<Enum.UIFlexAlignment>?,
|
|
||||||
alignitems: can<Enum.ItemLineAlignment>?,
|
|
||||||
spacing: can<number | UDim>?,
|
|
||||||
wraps: can<boolean>?,
|
|
||||||
|
|
||||||
[number]: Instance
|
|
||||||
}
|
|
||||||
|
|
||||||
local function layout(props: layout)
|
|
||||||
return container {
|
|
||||||
|
|
||||||
Size = UDim2.fromScale(1, 0),
|
|
||||||
AutomaticSize = Enum.AutomaticSize.Y,
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
Padding = function()
|
|
||||||
local spacing: number | UDim? = read(props.spacing)
|
|
||||||
|
|
||||||
return if typeof(spacing) == "number" then
|
|
||||||
UDim.new(0, spacing)
|
|
||||||
elseif typeof(spacing) == "UDim" then
|
|
||||||
spacing
|
|
||||||
elseif typeof(spacing) == "nil" then
|
|
||||||
UDim.new(0, 8)
|
|
||||||
else
|
|
||||||
error("incorrect spacing type")
|
|
||||||
end,
|
|
||||||
|
|
||||||
VerticalFlex = props.justifycontent,
|
|
||||||
ItemLineAlignment = props.alignitems,
|
|
||||||
Wraps = props.wraps
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
unpack(props)
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
return layout
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
--[[
|
|
||||||
|
|
||||||
Fast and easy to use padding utility to make controlling padding quick and simple.
|
|
||||||
|
|
||||||
]]
|
|
||||||
|
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
|
|
||||||
type can<T> = T | () -> T
|
|
||||||
type padding = {
|
|
||||||
padding: can<UDim>?,
|
|
||||||
x: can<UDim>?,
|
|
||||||
y: can<UDim>?,
|
|
||||||
left: can<UDim>?,
|
|
||||||
right: can<UDim>?,
|
|
||||||
top: can<UDim>?,
|
|
||||||
bottom: can<UDim>?
|
|
||||||
}
|
|
||||||
|
|
||||||
local function padding(props: padding)
|
|
||||||
|
|
||||||
local padding = props.padding or UDim.new(0, 8)
|
|
||||||
local x = props.x or padding
|
|
||||||
local y = props.y or padding
|
|
||||||
local left = props.left or x
|
|
||||||
local right = props.right or x
|
|
||||||
local top = props.top or y
|
|
||||||
local bottom = props.bottom or y
|
|
||||||
|
|
||||||
return create "UIPadding" {
|
|
||||||
PaddingLeft = left,
|
|
||||||
PaddingRight = right,
|
|
||||||
PaddingTop = top,
|
|
||||||
PaddingBottom = bottom,
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
return padding
|
|
||||||
|
|
@ -1,102 +0,0 @@
|
||||||
--[[
|
|
||||||
|
|
||||||
portal is used to render a component over other components.
|
|
||||||
it will find the nearest layer collector to parent itself and it's descendants
|
|
||||||
onto, and if inherit_layout is enabled, inherits the nearest guibase2d's size and
|
|
||||||
position properties.
|
|
||||||
|
|
||||||
]]
|
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local source = vide.source
|
|
||||||
local effect = vide.effect
|
|
||||||
local cleanup = vide.cleanup
|
|
||||||
local ref = vide.action
|
|
||||||
local read = vide.read
|
|
||||||
|
|
||||||
type can<T> = T | () -> T
|
|
||||||
type portal = {
|
|
||||||
--- controls if the portal should inherit the layout of the frame it's under
|
|
||||||
inherit_layout: can<boolean>?,
|
|
||||||
|
|
||||||
[number]: Instance,
|
|
||||||
}
|
|
||||||
|
|
||||||
local layout = 100_000
|
|
||||||
|
|
||||||
local function portal(props: portal)
|
|
||||||
|
|
||||||
local inherit_layout = props.inherit_layout
|
|
||||||
|
|
||||||
local nearest_gui_base = source(nil :: GuiBase2d?)
|
|
||||||
local nearest_layer_collector = source(nil :: LayerCollector?)
|
|
||||||
|
|
||||||
local size = source(UDim2.fromScale(1, 1))
|
|
||||||
local position = source(UDim2.fromScale(0, 0))
|
|
||||||
local reference = source(nil :: Configuration?)
|
|
||||||
|
|
||||||
-- this will create connections to update the size and position sources
|
|
||||||
effect(function()
|
|
||||||
local object = nearest_gui_base()
|
|
||||||
if not object then return end
|
|
||||||
|
|
||||||
local function update()
|
|
||||||
size(UDim2.fromOffset(object.AbsoluteSize.X, object.AbsoluteSize.Y))
|
|
||||||
position(UDim2.fromOffset(object.AbsolutePosition.X, object.AbsolutePosition.Y))
|
|
||||||
end
|
|
||||||
|
|
||||||
cleanup(object:GetPropertyChangedSignal("AbsoluteSize"):Connect(update))
|
|
||||||
cleanup(object:GetPropertyChangedSignal("AbsolutePosition"):Connect(update))
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- creates a container that is mounted to somewhere.
|
|
||||||
cleanup(vide.mount(function()
|
|
||||||
cleanup(create "Frame" {
|
|
||||||
|
|
||||||
Name = `Portal:{layout}`,
|
|
||||||
Parent = nearest_layer_collector,
|
|
||||||
AutoLocalize = false,
|
|
||||||
ZIndex = layout,
|
|
||||||
|
|
||||||
Size = function()
|
|
||||||
return if read(inherit_layout) == true then size()
|
|
||||||
else UDim2.fromScale(1, 1)
|
|
||||||
end,
|
|
||||||
Position = function()
|
|
||||||
return if read(inherit_layout) == true then position()
|
|
||||||
else UDim2.fromScale(0, 0)
|
|
||||||
end,
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
unpack(props)
|
|
||||||
|
|
||||||
})
|
|
||||||
end))
|
|
||||||
|
|
||||||
-- this is an anchor used to reference what gui base and layer collector to use.
|
|
||||||
return create "Configuration" {
|
|
||||||
Name = `PortalAnchor:{layout}`,
|
|
||||||
|
|
||||||
AncestryChanged = function()
|
|
||||||
local reference = reference()
|
|
||||||
if not reference then
|
|
||||||
nearest_gui_base(nil)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
nearest_gui_base(reference:FindFirstAncestorWhichIsA("GuiBase2d"))
|
|
||||||
nearest_layer_collector(reference:FindFirstAncestorWhichIsA("LayerCollector"))
|
|
||||||
end,
|
|
||||||
|
|
||||||
ref(function(instance)
|
|
||||||
layout += 1
|
|
||||||
reference(instance)
|
|
||||||
nearest_gui_base(instance:FindFirstAncestorWhichIsA("GuiBase2d"))
|
|
||||||
nearest_layer_collector(instance:FindFirstAncestorWhichIsA("LayerCollector"))
|
|
||||||
end)
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
return portal
|
|
||||||
|
|
@ -1,201 +0,0 @@
|
||||||
--[[
|
|
||||||
|
|
||||||
rounded_frame is a special kind of frame with UICorner controls for every
|
|
||||||
single corner.
|
|
||||||
|
|
||||||
]]
|
|
||||||
|
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local container = require(script.Parent.container)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local read = vide.read
|
|
||||||
|
|
||||||
type can<T> = T | () -> T
|
|
||||||
type rounded_frame = {
|
|
||||||
name: can<string>?,
|
|
||||||
size: can<UDim2>?,
|
|
||||||
position: can<UDim2>?,
|
|
||||||
anchor_point: can<Vector2>?,
|
|
||||||
|
|
||||||
topleft: can<UDim>?,
|
|
||||||
topright: can<UDim>?,
|
|
||||||
bottomleft: can<UDim>?,
|
|
||||||
bottomright: can<UDim>?,
|
|
||||||
|
|
||||||
color: can<Color3>?,
|
|
||||||
|
|
||||||
layout: vide.vFrame?,
|
|
||||||
|
|
||||||
[number]: any,
|
|
||||||
}
|
|
||||||
|
|
||||||
local function rounded_frame(props: rounded_frame)
|
|
||||||
local topleft = props.topleft or UDim.new()
|
|
||||||
local topright = props.topright or UDim.new()
|
|
||||||
local bottomleft = props.bottomleft or UDim.new()
|
|
||||||
local bottomright = props.bottomright or UDim.new()
|
|
||||||
|
|
||||||
local function corner(name: string, position: UDim2, anchor_point: Vector2, udim: can<UDim>)
|
|
||||||
return create "Frame" {
|
|
||||||
Name = name,
|
|
||||||
AutoLocalize = false,
|
|
||||||
|
|
||||||
Size = function()
|
|
||||||
return UDim2.new(read(udim), read(udim))
|
|
||||||
end,
|
|
||||||
Position = position,
|
|
||||||
AnchorPoint = anchor_point,
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
ClipsDescendants = true,
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
Name = "TopLeft",
|
|
||||||
AutoLocalize = false,
|
|
||||||
|
|
||||||
Size = UDim2.fromScale(2, 2),
|
|
||||||
Position = UDim2.fromScale(-anchor_point.X, -anchor_point.Y),
|
|
||||||
|
|
||||||
BackgroundColor3 = props.color,
|
|
||||||
ClipsDescendants = true,
|
|
||||||
|
|
||||||
create "UICorner" {
|
|
||||||
CornerRadius = udim
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
return create "Frame" {
|
|
||||||
|
|
||||||
Name = props.name or "RoundedFrame",
|
|
||||||
Size = props.size,
|
|
||||||
Position = props.position,
|
|
||||||
AnchorPoint = props.anchor_point,
|
|
||||||
|
|
||||||
BackgroundColor3 = props.color,
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
create "Folder" {
|
|
||||||
Name = "Corner",
|
|
||||||
|
|
||||||
corner("TopLeft", UDim2.fromScale(0, 0), Vector2.new(0, 0), topleft),
|
|
||||||
corner("TopRight", UDim2.fromScale(1, 0), Vector2.new(1, 0), topright),
|
|
||||||
corner("BottomLeft", UDim2.fromScale(0, 1), Vector2.new(0, 1), bottomleft),
|
|
||||||
corner("BottomRight", UDim2.fromScale(1, 1), Vector2.new(1, 1), bottomright),
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
AutoLocalize = false,
|
|
||||||
|
|
||||||
Name = "FrameLeft",
|
|
||||||
|
|
||||||
Size = function()
|
|
||||||
return UDim2.new(
|
|
||||||
0.5,
|
|
||||||
0,
|
|
||||||
1 - read(topleft).Scale - read(bottomleft).Scale,
|
|
||||||
- (read(topleft).Offset + read(bottomleft).Offset)
|
|
||||||
)
|
|
||||||
end,
|
|
||||||
Position = function()
|
|
||||||
return UDim2.new(
|
|
||||||
0, 0,
|
|
||||||
0.5 + read(topleft).Scale / 2 - read(bottomleft).Scale / 2,
|
|
||||||
0 + read(topleft).Offset / 2 - read(bottomleft).Offset / 2
|
|
||||||
)
|
|
||||||
end,
|
|
||||||
AnchorPoint = Vector2.new(0, 0.5),
|
|
||||||
|
|
||||||
BackgroundColor3 = props.color,
|
|
||||||
},
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
|
|
||||||
Name = "FrameRight",
|
|
||||||
AutoLocalize = false,
|
|
||||||
|
|
||||||
Size = function()
|
|
||||||
return UDim2.new(
|
|
||||||
0.5,
|
|
||||||
0,
|
|
||||||
1 - read(topright).Scale - read(bottomright).Scale,
|
|
||||||
- (read(topright).Offset + read(bottomright).Offset)
|
|
||||||
)
|
|
||||||
end,
|
|
||||||
Position = function()
|
|
||||||
return UDim2.new(
|
|
||||||
1, 0,
|
|
||||||
0.5 + read(topright).Scale / 2 - read(bottomright).Scale / 2,
|
|
||||||
0 + read(topright).Offset / 2 - read(bottomright).Offset / 2
|
|
||||||
)
|
|
||||||
end,
|
|
||||||
AnchorPoint = Vector2.new(1, 0.5),
|
|
||||||
|
|
||||||
BackgroundColor3 = props.color,
|
|
||||||
},
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
|
|
||||||
Name = "FrameTop",
|
|
||||||
AutoLocalize = false,
|
|
||||||
|
|
||||||
Size = function()
|
|
||||||
return UDim2.new(
|
|
||||||
1 - read(topleft).Scale - read(topright).Scale,
|
|
||||||
- (read(topleft).Offset + read(topright).Offset),
|
|
||||||
0.5,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
end,
|
|
||||||
Position = function()
|
|
||||||
return UDim2.new(
|
|
||||||
0.5 + read(topleft).Scale / 2 - read(topright).Scale / 2,
|
|
||||||
0 + read(topleft).Offset / 2 - read(topright).Offset / 2,
|
|
||||||
0, 0
|
|
||||||
)
|
|
||||||
end,
|
|
||||||
AnchorPoint = Vector2.new(0.5, 0),
|
|
||||||
|
|
||||||
BackgroundColor3 = props.color,
|
|
||||||
},
|
|
||||||
|
|
||||||
create "Frame" {
|
|
||||||
|
|
||||||
Name = "FrameBottom",
|
|
||||||
AutoLocalize = false,
|
|
||||||
|
|
||||||
Size = function()
|
|
||||||
return UDim2.new(
|
|
||||||
1 - read(bottomleft).Scale - read(bottomright).Scale,
|
|
||||||
- (read(bottomleft).Offset + read(bottomright).Offset),
|
|
||||||
0.5,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
end,
|
|
||||||
Position = function()
|
|
||||||
return UDim2.new(
|
|
||||||
0.5 + read(bottomleft).Scale / 2 - read(bottomright).Scale / 2,
|
|
||||||
0 + read(bottomleft).Offset / 2 - read(bottomright).Offset / 2,
|
|
||||||
1, 0
|
|
||||||
)
|
|
||||||
end,
|
|
||||||
AnchorPoint = Vector2.new(0.5, 1),
|
|
||||||
|
|
||||||
BackgroundColor3 = props.color,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
container {
|
|
||||||
unpack(props)
|
|
||||||
},
|
|
||||||
|
|
||||||
props.layout
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
return rounded_frame
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
--[[
|
|
||||||
|
|
||||||
Creates a container for a list of elements.
|
|
||||||
|
|
||||||
]]
|
|
||||||
|
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
local container = require(script.Parent.container)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
local read = vide.read
|
|
||||||
|
|
||||||
type can<T> = (() -> T) | T
|
|
||||||
type layout = {
|
|
||||||
|
|
||||||
justifycontent: can<Enum.UIFlexAlignment>?,
|
|
||||||
alignitems: can<Enum.ItemLineAlignment>?,
|
|
||||||
spacing: can<number | UDim>?,
|
|
||||||
wraps: can<boolean>?,
|
|
||||||
|
|
||||||
[number]: Instance
|
|
||||||
}
|
|
||||||
|
|
||||||
local function layout(props: layout)
|
|
||||||
return container {
|
|
||||||
|
|
||||||
Size = UDim2.fromScale(1, 0),
|
|
||||||
AutomaticSize = Enum.AutomaticSize.Y,
|
|
||||||
|
|
||||||
create "UIListLayout" {
|
|
||||||
Padding = function()
|
|
||||||
local spacing: number | UDim? = read(props.spacing)
|
|
||||||
|
|
||||||
return if typeof(spacing) == "number" then
|
|
||||||
UDim.new(0, spacing)
|
|
||||||
elseif typeof(spacing) == "UDim" then
|
|
||||||
spacing
|
|
||||||
elseif typeof(spacing) == "nil" then
|
|
||||||
UDim.new(0, 8)
|
|
||||||
else
|
|
||||||
error("incorrect spacing type")
|
|
||||||
end,
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
|
|
||||||
HorizontalFlex = props.justifycontent,
|
|
||||||
ItemLineAlignment = props.alignitems,
|
|
||||||
Wraps = props.wraps
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
unpack(props)
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
return layout
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
local vide = require(script.Parent.Parent.Parent.Parent.vide)
|
|
||||||
|
|
||||||
local create = vide.create
|
|
||||||
|
|
||||||
type can<T> = T | () -> T
|
|
||||||
type props = {
|
|
||||||
zindex: can<number>?,
|
|
||||||
transparency: can<number>?
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(props: props)
|
|
||||||
|
|
||||||
return create "UIStroke" {
|
|
||||||
|
|
||||||
Thickness = 2,
|
|
||||||
Color = Color3.new(0, 0, 0),
|
|
||||||
Transparency = 0.8
|
|
||||||
|
|
||||||
}
|
|
||||||
end
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue