@rbxts/app-forge 0.7.2-alpha.1 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -32,34 +32,14 @@ If you’ve ever ended up with tangled UI state, duplicated visibility logic, or
32
32
 
33
33
  ## 📦 Installation
34
34
 
35
- ### Using **bun**
36
-
37
35
  ```bash
38
36
  bun add @rbxts/app-forge
39
37
  ```
40
38
 
41
- Peer dependencies (choose one renderer):
39
+ Peer dependencies:
42
40
 
43
41
  ```bash
44
- bun add @rbxts/vide
45
- # or
46
- bun add @rbxts/react
47
- ```
48
-
49
- ---
50
-
51
- ### Using **npm**
52
-
53
- ```bash
54
- npm install @rbxts/app-forge
55
- ```
56
-
57
- Peer dependencies (choose one renderer):
58
-
59
- ```bash
60
- npm install @rbxts/vide
61
- # or
62
- npm install @rbxts/react
42
+ bun add @rbxts/vide @rbxts/loners-pretty-vide-utils
63
43
  ```
64
44
 
65
45
  ---
@@ -147,7 +127,9 @@ forge.mount(
147
127
  ResetOnSpawn={false}
148
128
  />
149
129
  ),
150
- props,
130
+ {
131
+ props,
132
+ },
151
133
  Players.LocalPlayer.WaitForChild("PlayerGui"),
152
134
  );
153
135
  ```
@@ -175,7 +157,9 @@ forge.mount(
175
157
  ResetOnSpawn={false}
176
158
  />
177
159
  ),
178
- props,
160
+ {
161
+ props,
162
+ },
179
163
  Players.LocalPlayer.WaitForChild("PlayerGui"),
180
164
  );
181
165
  ```
@@ -193,7 +177,9 @@ This:
193
177
  ```ts
194
178
  forge.mount(
195
179
  () => <screengui ResetOnSpawn={false} />,
196
- props,
180
+ {
181
+ props: {},
182
+ },
197
183
  playerGui,
198
184
  );
199
185
  ```
@@ -366,6 +352,7 @@ AppForge provides `forge.story` for **isolated rendering**, commonly used with *
366
352
  const forge = new CreateVideForge();
367
353
 
368
354
  return forge.story({
355
+ forge,
369
356
  props,
370
357
  config: {
371
358
  px: {
@@ -443,7 +430,7 @@ AppForge
443
430
 
444
431
  ## ⚛️ React Support (Planned)
445
432
 
446
- AppForge is designed as a **renderer-agnostic App Manager**.b
433
+ AppForge is designed as a **renderer-agnostic App Manager**.
447
434
 
448
435
  Currently:
449
436
 
@@ -8,10 +8,6 @@ export default class Renders extends Rules {
8
8
  render: Vide.Node;
9
9
  }>;
10
10
  constructor();
11
- /**
12
- * Entry point for mounting renders.
13
- * Decides render strategy based on props.
14
- */
15
11
  protected renderMount(this: AppForge, props: Types.Props.Main): Vide.Node;
16
12
  private renderNames;
17
13
  private collectByGroup;
@@ -35,28 +35,22 @@ do
35
35
  local render = _binding.render
36
36
  local forge = _binding.forge
37
37
  if not forge.__px then
38
- local _debug = forge.debug
39
38
  local _result = config
40
39
  if _result ~= nil then
41
- _result = _result.px
40
+ _result = _result.px.target
42
41
  end
43
- _debug:logTag("px", "global", "Initializing px scaling", _result)
44
42
  local _result_1 = config
45
43
  if _result_1 ~= nil then
46
- _result_1 = _result_1.px.target
44
+ _result_1 = _result_1.px.resolution
47
45
  end
48
46
  local _result_2 = config
49
47
  if _result_2 ~= nil then
50
- _result_2 = _result_2.px.resolution
48
+ _result_2 = _result_2.px.minScale
51
49
  end
52
- local _result_3 = config
53
- if _result_3 ~= nil then
54
- _result_3 = _result_3.px.minScale
55
- end
56
- usePx(_result_1, _result_2, _result_3)
50
+ usePx(_result, _result_1, _result_2)
57
51
  forge.__px = true
58
52
  else
59
- forge.debug:logTag("px", "global", "Skipped duplicate px initialization")
53
+ warn("Rendering twice making a second px")
60
54
  end
61
55
  if render then
62
56
  local _condition = render.name
@@ -64,35 +58,34 @@ do
64
58
  _condition = render.group
65
59
  end
66
60
  if _condition ~= "" and _condition then
67
- forge.debug:logTag("render", "global", "Rendering group by name", render)
68
61
  return forge:renderGroupByName(props)
69
- end
70
- local _value = render.names and render.group
71
- if _value ~= "" and _value then
72
- forge.debug:logTag("render", "global", "Rendering group by names", render)
73
- return forge:renderGroupByNames(props)
74
- end
75
- local _value_1 = render.name
76
- if _value_1 ~= "" and _value_1 then
77
- forge.debug:logTag("render", render.name, "Rendering single app")
78
- return forge:renderApp(props)
79
- end
80
- if render.names then
81
- forge.debug:logTag("render", "global", "Rendering multiple apps", render.names)
82
- return forge:renderApps(props)
83
- end
84
- local _value_2 = render.group
85
- if _value_2 ~= "" and _value_2 then
86
- forge.debug:logTag("render", "global", "Rendering group", render.group)
87
- return forge:renderGroup(props)
62
+ else
63
+ local _value = render.names and render.group
64
+ if _value ~= "" and _value then
65
+ return forge:renderGroupByNames(props)
66
+ else
67
+ local _result = render
68
+ if _result ~= nil then
69
+ _result = _result.name
70
+ end
71
+ if _result ~= "" and _result then
72
+ return forge:renderApp(props)
73
+ elseif render.names then
74
+ return forge:renderApps(props)
75
+ else
76
+ local _value_1 = render.group
77
+ if _value_1 ~= "" and _value_1 then
78
+ return forge:renderGroup(props)
79
+ end
80
+ end
81
+ end
88
82
  end
89
83
  end
90
- forge.debug:logTag("render", "global", "Rendering all apps")
91
84
  return self:renderAll(props)
92
85
  end
93
86
  function Renders:renderNames(props, names, forge)
94
87
  if #names == 0 then
95
- error("No app names provided to render", 2)
88
+ error("No app names provided to renderApps")
96
89
  end
97
90
  -- ▼ ReadonlyArray.map ▼
98
91
  local _newValue = table.create(#names)
@@ -148,21 +141,22 @@ do
148
141
  return _result
149
142
  end
150
143
  function Renders:renderApp(props)
151
- local _name = props.render
144
+ local _binding = props
145
+ local forge = _binding.forge
146
+ local render = _binding.render
147
+ local _name = render
152
148
  if _name ~= nil then
153
149
  _name = _name.name
154
150
  end
155
151
  local name = _name
156
152
  if not (name ~= "" and name) then
157
- error("renderApp requires an app name", 2)
153
+ error("App name is required to create instance")
158
154
  end
159
155
  local appClass = AppRegistry[name]
160
156
  if not appClass then
161
- error(`App "{name}" not registered`, 2)
157
+ error(`App "{name}" not registered`)
162
158
  end
163
- self.debug:time("render", name)
164
- if not (self.loaded[name] ~= nil) then
165
- self.debug:logTag("render", name, "Creating render instance")
159
+ if not (forge.loaded[name] ~= nil) then
166
160
  local render = appClass.constructor.new(props, name):render()
167
161
  apply(render)({
168
162
  Name = "Render",
@@ -175,18 +169,19 @@ do
175
169
  Size = UDim2.fromScale(1, 1),
176
170
  [0] = render,
177
171
  })
178
- local _loaded = self.loaded
172
+ local _loaded = forge.loaded
179
173
  local _arg1 = {
180
174
  container = container,
181
175
  render = render,
182
176
  }
183
177
  _loaded[name] = _arg1
184
- else
185
- self.debug:logTag("render", name, "Reusing existing render instance")
178
+ end
179
+ local element = forge.loaded[name]
180
+ if not element then
181
+ error(`Failed to create instance for app "{name}"`)
186
182
  end
187
183
  self:renderRules(name, props)
188
- self.debug:timeEnd("render", name)
189
- return self.loaded[name].container
184
+ return element.container
190
185
  end
191
186
  function Renders:renderApps(props)
192
187
  local _names = props.render
@@ -195,7 +190,7 @@ do
195
190
  end
196
191
  local names = _names
197
192
  if not names then
198
- error("renderApps requires app names", 2)
193
+ error("No app names provided")
199
194
  end
200
195
  return self:renderNames(props, names, self)
201
196
  end
@@ -206,7 +201,7 @@ do
206
201
  end
207
202
  local group = _group
208
203
  if not (group ~= "" and group) then
209
- error("renderGroup requires a group", 2)
204
+ error("No group provided")
210
205
  end
211
206
  local groups = self:normalizeGroups(group)
212
207
  return self:renderNames(props, self:collectByGroup(groups), self)
@@ -216,7 +211,7 @@ do
216
211
  local group = _binding.group
217
212
  local name = _binding.name
218
213
  if not (group ~= "" and group) or not (name ~= "" and name) then
219
- error("Invalid renderGroupByName call", 2)
214
+ error("Invalid renderGroupByName")
220
215
  end
221
216
  local groups = self:normalizeGroups(group)
222
217
  return self:renderNames(props, self:collectByGroup(groups, function(n)
@@ -228,7 +223,7 @@ do
228
223
  local group = _binding.group
229
224
  local names = _binding.names
230
225
  if not (group ~= "" and group) or not names then
231
- error("Invalid renderGroupByNames call", 2)
226
+ error("Invalid renderGroupByNames")
232
227
  end
233
228
  local groups = self:normalizeGroups(group)
234
229
  return self:renderNames(props, self:collectByGroup(groups, function(n)
@@ -6,43 +6,36 @@ local AppRegistry = TS.import(script, script.Parent.Parent.Parent, "decorator").
6
6
  local function ExclusiveGroupRule(entry, forge)
7
7
  local _entry = entry
8
8
  local entryApp = AppRegistry[_entry]
9
- local _group = entryApp
10
- if _group ~= nil then
11
- _group = _group.rules
12
- if _group ~= nil then
13
- _group = _group.exclusiveGroup
9
+ local _result = entryApp
10
+ if _result ~= nil then
11
+ _result = _result.rules
12
+ if _result ~= nil then
13
+ _result = _result.exclusiveGroup
14
14
  end
15
15
  end
16
- local group = _group
17
- if not (group ~= "" and group) then
16
+ if not (_result ~= "" and _result) then
18
17
  return nil
19
18
  end
20
- local entryVisible = forge:getSource(entry)()
21
- if not entryVisible then
19
+ local group = entryApp.rules.exclusiveGroup
20
+ local entrySource = forge:getSource(entry)()
21
+ if not entrySource then
22
22
  return nil
23
23
  end
24
- forge.debug:logTag("rules", entry, "Exclusive group activated", group)
25
24
  -- ▼ ReadonlyMap.forEach ▼
26
25
  local _callback = function(app, name)
27
26
  if name == entry then
28
27
  return nil
29
28
  end
30
- local _result = app.rules
31
- if _result ~= nil then
32
- _result = _result.exclusiveGroup
29
+ local _result_1 = app.rules
30
+ if _result_1 ~= nil then
31
+ _result_1 = _result_1.exclusiveGroup
33
32
  end
34
- if _result ~= group then
33
+ if _result_1 ~= group then
35
34
  return nil
36
35
  end
37
- local visible = forge:getSource(name)()
38
- if not visible then
39
- return nil
36
+ if forge:getSource(name)() then
37
+ forge:close(name, false)
40
38
  end
41
- forge.debug:logTag("rules", entry, "Closing app due to exclusive group", {
42
- closed = name,
43
- group = group,
44
- })
45
- forge:close(name, false)
46
39
  end
47
40
  for _k, _v in AppRegistry do
48
41
  _callback(_v, _k, AppRegistry)
@@ -25,40 +25,36 @@ do
25
25
  local _name = name
26
26
  local appClass = AppRegistry[_name]
27
27
  if not appClass then
28
- error(`renderRules: App "{name}" not registered`, 2)
28
+ error(`Failed to get class for app: {name} for renderRules`)
29
29
  end
30
- local rules = appClass.rules
31
- if not rules then
32
- return nil
30
+ local _result = appClass.rules
31
+ if _result ~= nil then
32
+ _result = _result.parent
33
33
  end
34
- -- Parent Anchor
35
- local _condition = rules.parent
34
+ local _condition = _result
36
35
  if _condition ~= "" and _condition then
37
- _condition = not rules.detach
36
+ _condition = not appClass.rules.detach
38
37
  end
39
38
  if _condition ~= "" and _condition then
40
- self.debug:logTag("rules", name, "Applying parent anchor", {
41
- parent = rules.parent,
42
- })
43
- self:anchor(name, rules.parent, props)
39
+ self:anchor(name, appClass.rules.parent, props)
40
+ end
41
+ local _result_1 = appClass.rules
42
+ if _result_1 ~= nil then
43
+ _result_1 = _result_1.index
44
44
  end
45
- -- Index
46
- if rules.index ~= nil then
47
- self.debug:logTag("rules", name, "Applying ZIndex", rules.index)
48
- self:index(name, rules.index)
45
+ if _result_1 ~= 0 and _result_1 == _result_1 and _result_1 then
46
+ self:index(name, appClass.rules.index)
49
47
  end
50
48
  end
51
49
  function Rules:checkRules(name)
52
50
  local _processing = self.processing
53
51
  local _name = name
54
52
  if _processing[_name] ~= nil then
55
- self.debug:logTag("rules", name, "Skipped rule processing (cycle detected)")
56
53
  return nil
57
54
  end
58
55
  local _processing_1 = self.processing
59
56
  local _name_1 = name
60
57
  _processing_1[_name_1] = true
61
- self.debug:logTag("rules", name, "Evaluating rules")
62
58
  TS.try(function()
63
59
  ParentRule(name, self)
64
60
  ExclusiveGroupRule(name, self)
@@ -4,7 +4,7 @@ local TS = _G[script]
4
4
  -- Components
5
5
  local AppRegistry = TS.import(script, script.Parent.Parent.Parent, "decorator").AppRegistry
6
6
  local function ParentRule(entry, forge)
7
- local entryVisible = forge:getSource(entry)()
7
+ local entrySource = forge:getSource(entry)()
8
8
  -- ▼ ReadonlyMap.forEach ▼
9
9
  local _callback = function(app, name)
10
10
  local rules = app.rules
@@ -14,14 +14,10 @@ local function ParentRule(entry, forge)
14
14
  if name == entry then
15
15
  return nil
16
16
  end
17
- local childVisible = forge:getSource(name)()
18
- if entryVisible or not childVisible then
19
- return nil
17
+ local childSource = forge:getSource(name)()
18
+ if not entrySource and childSource then
19
+ forge:close(name, false)
20
20
  end
21
- forge.debug:logTag("rules", entry, "Closing child app (parent closed)", {
22
- child = name,
23
- })
24
- forge:close(name, false)
25
21
  end
26
22
  for _k, _v in AppRegistry do
27
23
  _callback(_v, _k, AppRegistry)
@@ -2,15 +2,7 @@ import Vide from "@rbxts/vide";
2
2
  import type Types from "./types";
3
3
  import type AppForge from ".";
4
4
  export declare const AppRegistry: Map<string, Types.AppRegistry.Static>;
5
- /**
6
- * Registers a Vide App with AppForge.
7
- *
8
- * This runs at definition time and validates static configuration.
9
- */
10
5
  export declare function App<N extends AppNames>(props: Types.AppRegistry.Props<N>): <T extends new (props: Types.Props.Main, name: AppNames) => Args>(constructor: T) => T;
11
- /**
12
- * Base class for all AppForge Apps.
13
- */
14
6
  export declare abstract class Args {
15
7
  readonly forge: AppForge;
16
8
  readonly props: Types.Props.Class;
@@ -4,37 +4,13 @@ local TS = _G[script]
4
4
  -- Types
5
5
  -- Hooks
6
6
  local px = TS.import(script, script.Parent, "hooks", "usePx").px
7
- -- Debug
8
- local Logger = TS.import(script, script.Parent, "debug", "logger").default
9
- local logger = Logger.new("AppRegistry")
10
7
  local AppRegistry = {}
11
- --[[
12
- *
13
- * Registers a Vide App with AppForge.
14
- *
15
- * This runs at definition time and validates static configuration.
16
-
17
- ]]
18
8
  local function App(props)
19
9
  return function(constructor)
20
10
  local _name = props.name
21
11
  if AppRegistry[_name] ~= nil then
22
- logger:log("ERROR", "Duplicate App name detected", {
23
- name = props.name,
24
- })
25
- error(`Duplicate registered App name "{props.name}". ` .. `App names must be globally unique.`, 2)
12
+ error(`Duplicate registered App name "{props.name}"`)
26
13
  end
27
- local _value = props.name
28
- if not (_value ~= "" and _value) then
29
- logger:log("ERROR", "Attempted to register App without a name", props)
30
- error("App registration failed: missing app name", 2)
31
- end
32
- logger:log("DEBUG", "Registering App", {
33
- name = props.name,
34
- renderGroup = props.renderGroup,
35
- visible = props.visible,
36
- rules = props.rules,
37
- })
38
14
  local _name_1 = props.name
39
15
  local _arg1 = {
40
16
  constructor = constructor,
@@ -46,11 +22,6 @@ local function App(props)
46
22
  return constructor
47
23
  end
48
24
  end
49
- --[[
50
- *
51
- * Base class for all AppForge Apps.
52
-
53
- ]]
54
25
  local Args
55
26
  do
56
27
  Args = {}
@@ -58,20 +29,13 @@ do
58
29
  local _binding = props
59
30
  local forge = _binding.forge
60
31
  self.forge = forge
61
- self.name = name
62
32
  local _object = table.clone(props.props)
63
33
  setmetatable(_object, nil)
64
34
  _object.px = px
65
35
  _object.forge = forge
66
36
  self.props = _object
67
- local src = forge:getSource(name)
68
- if not src then
69
- logger:log("ERROR", "Missing visibility source for App", {
70
- name = name,
71
- })
72
- error(`Failed to retrieve visibility source for app "{name}"`, 2)
73
- end
74
- self.source = src
37
+ self.name = name
38
+ self.source = forge:getSource(name)
75
39
  end
76
40
  end
77
41
  return {
@@ -2,14 +2,10 @@
2
2
  local TS = _G[script]
3
3
  -- Components
4
4
  local Contexts = TS.import(script, script.Parent.Parent, "context").default
5
- -- Debug
6
- local Logger = TS.import(script, script.Parent.Parent, "debug", "logger").default
7
- local logger = Logger.new("useAppContext")
8
5
  local default = function()
9
6
  local context = Contexts.App()
10
7
  if not context then
11
- logger:log("ERROR", "Failed to retrieve App context")
12
- error(`Failed to retrieve App Props for Vide\n{debug.traceback()}`, 2)
8
+ error(`Failed to retrieve App Props for Vide {debug.traceback()}`)
13
9
  end
14
10
  return context
15
11
  end
@@ -11,7 +11,12 @@ type ConnectionLike = {
11
11
  disconnect(): void;
12
12
  } | (() => void);
13
13
  /**
14
- * Subscribes to an event-like object and auto-cleans up.
14
+ * Subscribes to an event-like object. The subscription is automatically
15
+ * disconnected when the scope cleans up.
16
+ *
17
+ * @param event The event-like object to subscribe to.
18
+ * @param listener The listener to subscribe with.
19
+ * @returns The connection object.
15
20
  */
16
21
  export declare function useEventListener<T extends EventLike>(event: T, listener: T extends EventLike<infer U> ? U : never): ReturnType<T>;
17
22
  export {};
@@ -1,12 +1,13 @@
1
1
  -- Compiled with roblox-ts v3.0.0
2
2
  local TS = _G[script]
3
3
  local cleanup = TS.import(script, TS.getModule(script, "@rbxts", "vide").src).cleanup
4
- -- Debug
5
- local Logger = TS.import(script, script.Parent.Parent, "debug", "logger").default
6
- local logger = Logger.new("useEventListener")
7
4
  local connect = function(event, callback)
8
5
  local _event = event
9
6
  if typeof(_event) == "RBXScriptSignal" then
7
+ -- With deferred events, a "hard disconnect" is necessary to avoid causing
8
+ -- state updates after a component unmounts. Use 'Connected' to check if
9
+ -- the connection is still valid before invoking the callback.
10
+ -- https://devforum.roblox.com/t/deferred-engine-events/2276564/99
10
11
  local connection
11
12
  connection = event:Connect(function(...)
12
13
  local args = { ... }
@@ -21,9 +22,9 @@ local connect = function(event, callback)
21
22
  return event:connect(callback)
22
23
  elseif event.subscribe ~= nil then
23
24
  return event:subscribe(callback)
25
+ else
26
+ error("Event-like object does not have a supported connect method.")
24
27
  end
25
- logger:log("ERROR", "Unsupported event-like object", event)
26
- error("Event-like object does not have a supported connect method.", 2)
27
28
  end
28
29
  local disconnect = function(connection)
29
30
  local _connection = connection
@@ -40,13 +41,18 @@ local disconnect = function(connection)
40
41
  elseif connection.disconnect ~= nil then
41
42
  connection:disconnect()
42
43
  else
43
- logger:log("WARN", "Unsupported connection-like object during cleanup", connection)
44
+ error("Connection-like object does not have a supported disconnect method.")
44
45
  end
45
46
  end
46
47
  end
47
48
  --[[
48
49
  *
49
- * Subscribes to an event-like object and auto-cleans up.
50
+ * Subscribes to an event-like object. The subscription is automatically
51
+ * disconnected when the scope cleans up.
52
+ *
53
+ * @param event The event-like object to subscribe to.
54
+ * @param listener The listener to subscribe with.
55
+ * @returns The connection object.
50
56
 
51
57
  ]]
52
58
  local function useEventListener(event, listener)
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Scaled pixel unit helper.
3
+ *
4
+ * - `px(12)` → rounded scaled pixels
5
+ * - `px.scale(12)` → raw scaled value
6
+ * - `px.even(12)` → even pixel values (useful for strokes/borders)
7
+ */
1
8
  export declare const px: ((value: number) => number) & {
2
9
  scale: (value: number) => number;
3
10
  even: (value: number) => number;
@@ -6,6 +13,7 @@ export declare const px: ((value: number) => number) & {
6
13
  };
7
14
  /**
8
15
  * Initializes global px scaling.
9
- * Must be called exactly once.
16
+ *
17
+ * Should be called exactly once at app mount.
10
18
  */
11
19
  export declare function usePx(target?: GuiObject | Camera, baseResolution?: Vector2, minScale?: number): void;
@@ -6,18 +6,25 @@ local Workspace = TS.import(script, TS.getModule(script, "@rbxts", "services")).
6
6
  local source = TS.import(script, TS.getModule(script, "@rbxts", "vide").src).source
7
7
  -- Helpers
8
8
  local useEventListener = TS.import(script, script.Parent, "useEventListener").useEventListener
9
- -- Debug
10
- local Logger = TS.import(script, script.Parent.Parent, "debug", "logger").default
11
- local logger = Logger.new("usePx")
12
9
  --* Default reference resolution for px calculations
13
10
  local BASE_RESOLUTION = source(Vector2.new(1920, 1080))
14
11
  --* Minimum allowed scale to prevent unreadable UI
15
12
  local MIN_SCALE = source(0.5)
16
- --* 0 = width-based, 1 = height-based
13
+ --[[
14
+ *
15
+ * Interpolates between width- and height-based scaling.
16
+ * 0 = width-driven, 1 = height-driven
17
+
18
+ ]]
17
19
  local DOMINANT_AXIS = 0.5
18
20
  local TARGET = source(Workspace.CurrentCamera)
19
21
  local SCALE = source(1)
20
22
  local INITIALIZED = false
23
+ --[[
24
+ *
25
+ * Assigns a call signature to an object.
26
+
27
+ ]]
21
28
  local function callable(callback, object)
22
29
  return setmetatable(object, {
23
30
  __call = function(_, ...)
@@ -26,6 +33,15 @@ local function callable(callback, object)
26
33
  end,
27
34
  })
28
35
  end
36
+ --[[
37
+ *
38
+ * Scaled pixel unit helper.
39
+ *
40
+ * - `px(12)` → rounded scaled pixels
41
+ * - `px.scale(12)` → raw scaled value
42
+ * - `px.even(12)` → even pixel values (useful for strokes/borders)
43
+
44
+ ]]
29
45
  local px = callable(function(value)
30
46
  return math.round(value * SCALE())
31
47
  end, {
@@ -42,6 +58,11 @@ end, {
42
58
  return math.ceil(value * SCALE())
43
59
  end,
44
60
  })
61
+ --[[
62
+ *
63
+ * Recalculates the current scale factor based on the target size.
64
+
65
+ ]]
45
66
  local function calculateScale()
46
67
  local target = TARGET()
47
68
  if not target then
@@ -65,20 +86,16 @@ end
65
86
  --[[
66
87
  *
67
88
  * Initializes global px scaling.
68
- * Must be called exactly once.
89
+ *
90
+ * Should be called exactly once at app mount.
69
91
 
70
92
  ]]
71
93
  local function usePx(target, baseResolution, minScale)
72
94
  if INITIALIZED then
73
- logger:log("WARN", "usePx() called more than once")
95
+ warn("usePx() may only be called once")
74
96
  return nil
75
97
  end
76
98
  INITIALIZED = true
77
- logger:log("DEBUG", "Initializing px scaling", {
78
- target = target,
79
- baseResolution = baseResolution,
80
- minScale = minScale,
81
- })
82
99
  if baseResolution then
83
100
  BASE_RESOLUTION(baseResolution)
84
101
  end
@@ -90,7 +107,7 @@ local function usePx(target, baseResolution, minScale)
90
107
  end
91
108
  local resolvedTarget = TARGET()
92
109
  if not resolvedTarget then
93
- logger:log("WARN", "usePx(): no valid target to observe")
110
+ warn("usePx(): no valid target to observe")
94
111
  return nil
95
112
  end
96
113
  local signal = if resolvedTarget:IsA("Camera") then resolvedTarget:GetPropertyChangedSignal("ViewportSize") else resolvedTarget:GetPropertyChangedSignal("AbsoluteSize")
@@ -1,7 +1,6 @@
1
1
  import Vide from "@rbxts/vide";
2
2
  import Renders from "./classes/renders";
3
3
  import Types from "./types";
4
- import { Logger, Debugger } from "./debug";
5
4
  type Destructor = () => void;
6
5
  type Loaded = {
7
6
  container: Vide.Node;
@@ -9,19 +8,17 @@ type Loaded = {
9
8
  anchor?: Vide.Node;
10
9
  };
11
10
  export default class AppForge extends Renders {
12
- readonly logger: Logger;
13
- readonly debug: Debugger;
14
11
  protected sources: Map<string, Vide.Source<boolean>>;
15
12
  protected loaded: Map<string, Loaded>;
16
13
  protected innerMount?: Destructor;
17
14
  protected __px: boolean;
18
15
  constructor();
19
- protected createSource(name: AppNames): void;
20
- getSource(name: AppNames): Vide.Source<boolean>;
16
+ protected createSource(name: AppNames): typeof Vide.source | undefined;
21
17
  isLoaded(name: AppNames): boolean;
22
18
  bind(name: AppNames, value: Vide.Source<boolean>): void;
23
19
  anchor(name: AppNames, anchorName: AppNames, props: Types.Props.Main): void;
24
20
  index(name: AppNames, index: number): void;
21
+ getSource(name: AppNames): Vide.Source<boolean>;
25
22
  set(name: AppNames, value: boolean, rules?: boolean): void;
26
23
  open(name: AppNames, rules?: boolean): void;
27
24
  close(name: AppNames, rules?: boolean): void;
@@ -14,10 +14,6 @@ local untrack = _vide.untrack
14
14
  local Renders = TS.import(script, script, "classes", "renders").default
15
15
  -- Helpers
16
16
  local AppRegistry = TS.import(script, script, "decorator").AppRegistry
17
- -- Debug
18
- local _debug = TS.import(script, script, "debug")
19
- local Logger = _debug.Logger
20
- local Debugger = _debug.Debugger
21
17
  local AppForge
22
18
  do
23
19
  local super = Renders
@@ -34,10 +30,6 @@ do
34
30
  end
35
31
  function AppForge:constructor()
36
32
  super.constructor(self)
37
- self.logger = Logger.new("AppForge")
38
- self.debug = Debugger.new(function(level, msg, data, trace)
39
- return self.logger:log(level, msg, data, trace)
40
- end)
41
33
  self.sources = {}
42
34
  self.loaded = {}
43
35
  self.__px = false
@@ -54,48 +46,22 @@ do
54
46
  local _name = name
55
47
  local app = AppRegistry[_name]
56
48
  if not app then
57
- self.logger:log("ERROR", "App not registered while creating source", {
58
- name = name,
59
- })
60
- return nil
49
+ error(`App "{name}" not registered`)
61
50
  end
62
51
  local _sources = self.sources
63
52
  local _name_1 = name
64
53
  if _sources[_name_1] ~= nil then
65
54
  return nil
66
55
  end
67
- local _debug_1 = self.debug
56
+ local _sources_1 = self.sources
68
57
  local _exp = name
69
- local _object = {}
70
- local _left = "default"
71
58
  local _condition = app.visible
72
59
  if _condition == nil then
73
60
  _condition = false
74
61
  end
75
- _object[_left] = _condition
76
- _debug_1:logTag("state", _exp, "Creating visibility source", _object)
77
- local _sources_1 = self.sources
78
- local _exp_1 = name
79
- local _condition_1 = app.visible
80
- if _condition_1 == nil then
81
- _condition_1 = false
82
- end
83
- local _arg1 = source(_condition_1)
84
- _sources_1[_exp_1] = _arg1
85
- end
86
- function AppForge:getSource(name)
87
- local _sources = self.sources
88
- local _name = name
89
- if not (_sources[_name] ~= nil) then
90
- self:createSource(name)
91
- end
92
- local _sources_1 = self.sources
93
- local _name_1 = name
94
- local src = _sources_1[_name_1]
95
- if not src then
96
- error(`AppForge invariant broken: missing visibility source for {name}`, 2)
97
- end
98
- return src
62
+ local _arg1 = source(_condition)
63
+ _sources_1[_exp] = _arg1
64
+ return source
99
65
  end
100
66
  function AppForge:isLoaded(name)
101
67
  local _loaded = self.loaded
@@ -104,7 +70,6 @@ do
104
70
  end
105
71
  function AppForge:bind(name, value)
106
72
  if not RunService:IsRunning() then
107
- self.debug:logTag("state", name, "Binding external visibility source")
108
73
  local _sources = self.sources
109
74
  local _name = name
110
75
  local _value = value
@@ -116,26 +81,17 @@ do
116
81
  end)
117
82
  end)
118
83
  else
119
- self.logger:log("WARN", "forge.bind called while game is running", {
120
- name = name,
121
- })
84
+ warn("forge.bind is used for studio when game isnt running")
122
85
  end
123
86
  end
124
87
  function AppForge:anchor(name, anchorName, props)
125
88
  if name == anchorName then
126
- self.logger:log("ERROR", "Attempted to anchor app to itself", {
127
- name = name,
128
- })
129
- return nil
89
+ error(`Tried to anchor an App to itself`)
130
90
  end
131
91
  local _anchorName = anchorName
132
92
  local anchorApp = AppRegistry[_anchorName]
133
93
  if not anchorApp then
134
- self.logger:log("ERROR", "Anchor parent not registered", {
135
- child = name,
136
- parent = anchorName,
137
- })
138
- return nil
94
+ error(`Failed to get class for {anchorName} from AppRegistry for anchor`)
139
95
  end
140
96
  local _loaded = self.loaded
141
97
  local _name = name
@@ -145,17 +101,11 @@ do
145
101
  end
146
102
  local render = _render
147
103
  if not render then
148
- self.debug:logTag("rules", name, "Anchor skipped (child not rendered yet)", {
149
- parent = anchorName,
150
- })
151
- return nil
104
+ error(`Failed to get {name} from this.loaded for anchor to {anchorName}`)
152
105
  end
153
- self.debug:logTag("rules", name, "Anchoring to parent", {
154
- parent = anchorName,
155
- })
156
106
  local anchor = anchorApp.constructor.new(props, anchorName):render()
157
- for _, child in anchor:GetDescendants() do
158
- child:Destroy()
107
+ for _, children in anchor:GetDescendants() do
108
+ children:Destroy()
159
109
  end
160
110
  apply(anchor)({
161
111
  Name = "Anchor",
@@ -166,7 +116,7 @@ do
166
116
  local _name_1 = name
167
117
  local prev = _loaded_1[_name_1]
168
118
  if not prev then
169
- error(`AppForge invariant broken: missing loaded app for {name}`, 2)
119
+ error(`Failed to retreive prev loaded data for {name}`)
170
120
  end
171
121
  apply(prev.container)({
172
122
  [0] = anchor,
@@ -183,19 +133,22 @@ do
183
133
  local _name = name
184
134
  local loaded = _loaded[_name]
185
135
  if not loaded then
186
- self.logger:log("WARN", "ZIndex skipped (app not loaded)", {
187
- name = name,
188
- index = index,
189
- })
190
- return nil
136
+ error(`Failed to retreive loaded data for app: {name}`)
191
137
  end
192
- self.debug:logTag("rules", name, "Applying ZIndex", {
193
- index = index,
194
- })
195
138
  apply(loaded.container)({
196
139
  ZIndex = index,
197
140
  })
198
141
  end
142
+ function AppForge:getSource(name)
143
+ local _sources = self.sources
144
+ local _name = name
145
+ if not (_sources[_name] ~= nil) then
146
+ self:createSource(name)
147
+ end
148
+ local _sources_1 = self.sources
149
+ local _name_1 = name
150
+ return _sources_1[_name_1]
151
+ end
199
152
  function AppForge:set(name, value, rules)
200
153
  if rules == nil then
201
154
  rules = true
@@ -209,21 +162,10 @@ do
209
162
  local _name_1 = name
210
163
  src = _sources_1[_name_1]
211
164
  end
212
- if not src then
213
- self.logger:log("ERROR", "Failed to set visibility (missing source)", {
214
- name = name,
215
- })
216
- return nil
217
- end
218
- local prev = src()
219
- if prev == value then
165
+ if src() == value then
220
166
  return nil
221
167
  end
222
168
  src(value)
223
- self.debug:logTag("state", name, "Visibility changed", {
224
- from = prev,
225
- to = value,
226
- })
227
169
  if rules then
228
170
  self:checkRules(name)
229
171
  end
@@ -247,7 +189,6 @@ do
247
189
  self:set(name, not self:getSource(name)(), rules)
248
190
  end
249
191
  function AppForge:story(props)
250
- self.debug:logTag("lifecycle", "story", "Creating story mount")
251
192
  local Container = create("Frame")({
252
193
  Name = "Story Container",
253
194
  BackgroundTransparency = 1,
@@ -261,7 +202,6 @@ do
261
202
  return Container
262
203
  end
263
204
  function AppForge:mount(callback, props, target)
264
- self.debug:logTag("lifecycle", "mount", "Mounting AppForge")
265
205
  local Container = callback()
266
206
  self.innerMount = mount(function()
267
207
  apply(Container)({
@@ -272,7 +212,6 @@ do
272
212
  return self.innerMount
273
213
  end
274
214
  function AppForge:unMount()
275
- self.debug:logTag("lifecycle", "unmount", "Unmounting AppForge")
276
215
  local _result = self.innerMount
277
216
  if _result ~= nil then
278
217
  _result()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rbxts/app-forge",
3
- "version": "0.7.2-alpha.1",
3
+ "version": "0.7.2",
4
4
  "description": "An App Manager for Vide",
5
5
  "main": "out/init.lua",
6
6
  "types": "out/index.d.ts",
@@ -1,15 +0,0 @@
1
- export type DebugTag = string;
2
- type LogFn = (level: "DEBUG" | "PERF", message: string, data?: unknown, traceback?: string) => void;
3
- export default class Debugger {
4
- private readonly log;
5
- private enabled;
6
- private timers;
7
- constructor(log: LogFn);
8
- enable(tag: DebugTag): void;
9
- disable(tag: DebugTag): void;
10
- isEnabled(tag: DebugTag): boolean;
11
- logTag(tag: DebugTag, app: AppNames, message: string, data?: unknown): void;
12
- time(tag: DebugTag, app: AppNames): void;
13
- timeEnd(tag: DebugTag, app: AppNames): void;
14
- }
15
- export {};
@@ -1,83 +0,0 @@
1
- -- Compiled with roblox-ts v3.0.0
2
- local TS = _G[script]
3
- -- Services
4
- local RunService = TS.import(script, TS.getModule(script, "@rbxts", "services")).RunService
5
- local Debugger
6
- do
7
- Debugger = setmetatable({}, {
8
- __tostring = function()
9
- return "Debugger"
10
- end,
11
- })
12
- Debugger.__index = Debugger
13
- function Debugger.new(...)
14
- local self = setmetatable({}, Debugger)
15
- return self:constructor(...) or self
16
- end
17
- function Debugger:constructor(log)
18
- self.log = log
19
- self.enabled = {}
20
- self.timers = {}
21
- end
22
- function Debugger:enable(tag)
23
- local _enabled = self.enabled
24
- local _tag = tag
25
- _enabled[_tag] = true
26
- end
27
- function Debugger:disable(tag)
28
- local _enabled = self.enabled
29
- local _tag = tag
30
- _enabled[_tag] = nil
31
- end
32
- function Debugger:isEnabled(tag)
33
- local _enabled = self.enabled
34
- local _tag = tag
35
- return _enabled[_tag] ~= nil
36
- end
37
- function Debugger:logTag(tag, app, message, data)
38
- if not RunService:IsStudio() then
39
- return nil
40
- end
41
- local _enabled = self.enabled
42
- local _tag = tag
43
- if not (_enabled[_tag] ~= nil) then
44
- return nil
45
- end
46
- self.log("DEBUG", `[{tag}][{app}] {message}`, data, debug.traceback(nil, 3))
47
- end
48
- function Debugger:time(tag, app)
49
- if not RunService:IsStudio() then
50
- return nil
51
- end
52
- local _enabled = self.enabled
53
- local _tag = tag
54
- if not (_enabled[_tag] ~= nil) then
55
- return nil
56
- end
57
- local _timers = self.timers
58
- local _arg0 = `{tag}:{app}`
59
- local _arg1 = os.clock()
60
- _timers[_arg0] = _arg1
61
- end
62
- function Debugger:timeEnd(tag, app)
63
- if not RunService:IsStudio() then
64
- return nil
65
- end
66
- local _enabled = self.enabled
67
- local _tag = tag
68
- if not (_enabled[_tag] ~= nil) then
69
- return nil
70
- end
71
- local key = `{tag}:{app}`
72
- local start = self.timers[key]
73
- if start == nil then
74
- return nil
75
- end
76
- self.timers[key] = nil
77
- local elapsed = os.clock() - start
78
- self.log("PERF", `[{tag}][{app}] {string.format("%.3fms", elapsed * 1000)}`)
79
- end
80
- end
81
- return {
82
- default = Debugger,
83
- }
@@ -1,3 +0,0 @@
1
- import Debugger from "./debugger";
2
- import Logger from "./logger";
3
- export { Debugger, Logger };
@@ -1,8 +0,0 @@
1
- -- Compiled with roblox-ts v3.0.0
2
- local TS = _G[script]
3
- local Debugger = TS.import(script, script, "debugger").default
4
- local Logger = TS.import(script, script, "logger").default
5
- return {
6
- Debugger = Debugger,
7
- Logger = Logger,
8
- }
@@ -1,5 +0,0 @@
1
- export default class Logger {
2
- private readonly scope;
3
- constructor(scope: string);
4
- log(level: "DEBUG" | "PERF" | "INFO" | "WARN" | "ERROR", message: string, data?: unknown, traceback?: string): void;
5
- }
@@ -1,37 +0,0 @@
1
- -- Compiled with roblox-ts v3.0.0
2
- local TS = _G[script]
3
- -- debug/logger.ts
4
- local RunService = TS.import(script, TS.getModule(script, "@rbxts", "services")).RunService
5
- local Logger
6
- do
7
- Logger = setmetatable({}, {
8
- __tostring = function()
9
- return "Logger"
10
- end,
11
- })
12
- Logger.__index = Logger
13
- function Logger.new(...)
14
- local self = setmetatable({}, Logger)
15
- return self:constructor(...) or self
16
- end
17
- function Logger:constructor(scope)
18
- self.scope = scope
19
- end
20
- function Logger:log(level, message, data, traceback)
21
- if not RunService:IsStudio() then
22
- return nil
23
- end
24
- local prefix = `[{self.scope}][{level}]`
25
- if data ~= nil then
26
- print(prefix, message, data)
27
- else
28
- print(prefix, message)
29
- end
30
- if traceback ~= "" and traceback then
31
- print(traceback)
32
- end
33
- end
34
- end
35
- return {
36
- default = Logger,
37
- }