@rbxts/app-forge 0.7.1 → 0.7.2-alpha.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,14 +32,34 @@ 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
+
35
37
  ```bash
36
38
  bun add @rbxts/app-forge
37
39
  ```
38
40
 
39
- Peer dependencies:
41
+ Peer dependencies (choose one renderer):
40
42
 
41
43
  ```bash
42
- bun add @rbxts/vide @rbxts/loners-pretty-vide-utils
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
43
63
  ```
44
64
 
45
65
  ---
@@ -127,9 +147,7 @@ forge.mount(
127
147
  ResetOnSpawn={false}
128
148
  />
129
149
  ),
130
- {
131
- props,
132
- },
150
+ props,
133
151
  Players.LocalPlayer.WaitForChild("PlayerGui"),
134
152
  );
135
153
  ```
@@ -157,9 +175,7 @@ forge.mount(
157
175
  ResetOnSpawn={false}
158
176
  />
159
177
  ),
160
- {
161
- props,
162
- },
178
+ props,
163
179
  Players.LocalPlayer.WaitForChild("PlayerGui"),
164
180
  );
165
181
  ```
@@ -177,9 +193,7 @@ This:
177
193
  ```ts
178
194
  forge.mount(
179
195
  () => <screengui ResetOnSpawn={false} />,
180
- {
181
- props: {},
182
- },
196
+ props,
183
197
  playerGui,
184
198
  );
185
199
  ```
@@ -352,7 +366,6 @@ AppForge provides `forge.story` for **isolated rendering**, commonly used with *
352
366
  const forge = new CreateVideForge();
353
367
 
354
368
  return forge.story({
355
- forge,
356
369
  props,
357
370
  config: {
358
371
  px: {
@@ -430,7 +443,7 @@ AppForge
430
443
 
431
444
  ## ⚛️ React Support (Planned)
432
445
 
433
- AppForge is designed as a **renderer-agnostic App Manager**.
446
+ AppForge is designed as a **renderer-agnostic App Manager**.b
434
447
 
435
448
  Currently:
436
449
 
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Scaled pixel unit helper.
3
+ */
4
+ export declare const px: ((value: number) => number) & {
5
+ scale: (value: number) => number;
6
+ even: (value: number) => number;
7
+ floor: (value: number) => number;
8
+ ceil: (value: number) => number;
9
+ };
10
+ /**
11
+ * Initializes global px scaling.
12
+ *
13
+ * Should be called exactly once at app mount.
14
+ */
15
+ export declare function usePx(target?: GuiObject | Camera, baseResolution?: Vector2, minScale?: number): void;
@@ -0,0 +1,117 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ -- Services
4
+ local Workspace = TS.import(script, TS.getModule(script, "@rbxts", "services")).Workspace
5
+ -- React
6
+ local useEffect = TS.import(script, TS.getModule(script, "@rbxts", "react")).useEffect
7
+ --* Default reference resolution for px calculations
8
+ local BASE_RESOLUTION = Vector2.new(1920, 1080)
9
+ --* Minimum allowed scale to prevent unreadable UI
10
+ local MIN_SCALE = 0.5
11
+ --[[
12
+ *
13
+ * Interpolates between width- and height-based scaling.
14
+ * 0 = width-driven, 1 = height-driven
15
+
16
+ ]]
17
+ local DOMINANT_AXIS = 0.5
18
+ local TARGET = Workspace.CurrentCamera
19
+ local SCALE = 1
20
+ local GLOBAL_INITIALIZED = false
21
+ --[[
22
+ *
23
+ * Assigns a call signature to an object.
24
+
25
+ ]]
26
+ local function callable(callback, object)
27
+ return setmetatable(object, {
28
+ __call = function(_, ...)
29
+ local args = { ... }
30
+ return callback(unpack(args))
31
+ end,
32
+ })
33
+ end
34
+ --[[
35
+ *
36
+ * Scaled pixel unit helper.
37
+
38
+ ]]
39
+ local px = callable(function(value)
40
+ return math.round(value * SCALE)
41
+ end, {
42
+ scale = function(value)
43
+ return value * SCALE
44
+ end,
45
+ even = function(value)
46
+ return math.round(value * SCALE * 0.5) * 2
47
+ end,
48
+ floor = function(value)
49
+ return math.floor(value * SCALE)
50
+ end,
51
+ ceil = function(value)
52
+ return math.ceil(value * SCALE)
53
+ end,
54
+ })
55
+ --[[
56
+ *
57
+ * Recalculates the current scale factor.
58
+
59
+ ]]
60
+ local function calculateScale()
61
+ local target = TARGET
62
+ if not target then
63
+ return nil
64
+ end
65
+ local size = if target:IsA("Camera") then target.ViewportSize elseif target:IsA("GuiObject") then target.AbsoluteSize else nil
66
+ if not size then
67
+ return nil
68
+ end
69
+ if BASE_RESOLUTION.X <= 0 or BASE_RESOLUTION.Y <= 0 then
70
+ return nil
71
+ end
72
+ local width = math.log(size.X / BASE_RESOLUTION.X, 2)
73
+ local height = math.log(size.Y / BASE_RESOLUTION.Y, 2)
74
+ local centered = width + (height - width) * DOMINANT_AXIS
75
+ local scale = 2 ^ centered
76
+ SCALE = math.max(scale, MIN_SCALE)
77
+ end
78
+ --[[
79
+ *
80
+ * Initializes global px scaling.
81
+ *
82
+ * Should be called exactly once at app mount.
83
+
84
+ ]]
85
+ local function usePx(target, baseResolution, minScale)
86
+ useEffect(function()
87
+ if GLOBAL_INITIALIZED then
88
+ warn("usePx() may only be called once globally")
89
+ return nil
90
+ end
91
+ GLOBAL_INITIALIZED = true
92
+ if baseResolution then
93
+ BASE_RESOLUTION = baseResolution
94
+ end
95
+ if minScale ~= nil then
96
+ MIN_SCALE = minScale
97
+ end
98
+ if target then
99
+ TARGET = target
100
+ end
101
+ local resolvedTarget = TARGET
102
+ if not resolvedTarget then
103
+ warn("usePx(): no valid target to observe")
104
+ return nil
105
+ end
106
+ local signal = if resolvedTarget:IsA("Camera") then resolvedTarget:GetPropertyChangedSignal("ViewportSize") else resolvedTarget:GetPropertyChangedSignal("AbsoluteSize")
107
+ local connection = signal:Connect(calculateScale)
108
+ calculateScale()
109
+ return function()
110
+ connection:Disconnect()
111
+ end
112
+ end, {})
113
+ end
114
+ return {
115
+ usePx = usePx,
116
+ px = px,
117
+ }
@@ -8,6 +8,10 @@ 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
+ */
11
15
  protected renderMount(this: AppForge, props: Types.Props.Main): Vide.Node;
12
16
  private renderNames;
13
17
  private collectByGroup;
@@ -1,14 +1,14 @@
1
1
  -- Compiled with roblox-ts v3.0.0
2
2
  local TS = _G[script]
3
- -- Services
4
3
  -- Packages
5
- local usePx = TS.import(script, TS.getModule(script, "@rbxts", "loners-pretty-vide-utils").out).usePx
6
4
  local _vide = TS.import(script, TS.getModule(script, "@rbxts", "vide").src)
7
5
  local apply = _vide.apply
8
6
  local create = _vide.create
9
7
  -- Types
10
8
  -- Components
11
9
  local AppRegistry = TS.import(script, script.Parent.Parent, "decorator").AppRegistry
10
+ -- Hooks
11
+ local usePx = TS.import(script, script.Parent.Parent, "hooks", "usePx").usePx
12
12
  -- Classes
13
13
  local Rules = TS.import(script, script.Parent, "rules").default
14
14
  local Renders
@@ -35,22 +35,28 @@ 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
38
39
  local _result = config
39
40
  if _result ~= nil then
40
- _result = _result.px.target
41
+ _result = _result.px
41
42
  end
43
+ _debug:logTag("px", "global", "Initializing px scaling", _result)
42
44
  local _result_1 = config
43
45
  if _result_1 ~= nil then
44
- _result_1 = _result_1.px.resolution
46
+ _result_1 = _result_1.px.target
45
47
  end
46
48
  local _result_2 = config
47
49
  if _result_2 ~= nil then
48
- _result_2 = _result_2.px.minScale
50
+ _result_2 = _result_2.px.resolution
49
51
  end
50
- usePx(_result, _result_1, _result_2)
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)
51
57
  forge.__px = true
52
58
  else
53
- warn("Rendering twice making a second px")
59
+ forge.debug:logTag("px", "global", "Skipped duplicate px initialization")
54
60
  end
55
61
  if render then
56
62
  local _condition = render.name
@@ -58,34 +64,35 @@ do
58
64
  _condition = render.group
59
65
  end
60
66
  if _condition ~= "" and _condition then
67
+ forge.debug:logTag("render", "global", "Rendering group by name", render)
61
68
  return forge:renderGroupByName(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
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)
82
88
  end
83
89
  end
90
+ forge.debug:logTag("render", "global", "Rendering all apps")
84
91
  return self:renderAll(props)
85
92
  end
86
93
  function Renders:renderNames(props, names, forge)
87
94
  if #names == 0 then
88
- error("No app names provided to renderApps")
95
+ error("No app names provided to render", 2)
89
96
  end
90
97
  -- ▼ ReadonlyArray.map ▼
91
98
  local _newValue = table.create(#names)
@@ -141,22 +148,21 @@ do
141
148
  return _result
142
149
  end
143
150
  function Renders:renderApp(props)
144
- local _binding = props
145
- local forge = _binding.forge
146
- local render = _binding.render
147
- local _name = render
151
+ local _name = props.render
148
152
  if _name ~= nil then
149
153
  _name = _name.name
150
154
  end
151
155
  local name = _name
152
156
  if not (name ~= "" and name) then
153
- error("App name is required to create instance")
157
+ error("renderApp requires an app name", 2)
154
158
  end
155
159
  local appClass = AppRegistry[name]
156
160
  if not appClass then
157
- error(`App "{name}" not registered`)
161
+ error(`App "{name}" not registered`, 2)
158
162
  end
159
- if not (forge.loaded[name] ~= nil) then
163
+ self.debug:time("render", name)
164
+ if not (self.loaded[name] ~= nil) then
165
+ self.debug:logTag("render", name, "Creating render instance")
160
166
  local render = appClass.constructor.new(props, name):render()
161
167
  apply(render)({
162
168
  Name = "Render",
@@ -169,19 +175,18 @@ do
169
175
  Size = UDim2.fromScale(1, 1),
170
176
  [0] = render,
171
177
  })
172
- local _loaded = forge.loaded
178
+ local _loaded = self.loaded
173
179
  local _arg1 = {
174
180
  container = container,
175
181
  render = render,
176
182
  }
177
183
  _loaded[name] = _arg1
178
- end
179
- local element = forge.loaded[name]
180
- if not element then
181
- error(`Failed to create instance for app "{name}"`)
184
+ else
185
+ self.debug:logTag("render", name, "Reusing existing render instance")
182
186
  end
183
187
  self:renderRules(name, props)
184
- return element.container
188
+ self.debug:timeEnd("render", name)
189
+ return self.loaded[name].container
185
190
  end
186
191
  function Renders:renderApps(props)
187
192
  local _names = props.render
@@ -190,7 +195,7 @@ do
190
195
  end
191
196
  local names = _names
192
197
  if not names then
193
- error("No app names provided")
198
+ error("renderApps requires app names", 2)
194
199
  end
195
200
  return self:renderNames(props, names, self)
196
201
  end
@@ -201,7 +206,7 @@ do
201
206
  end
202
207
  local group = _group
203
208
  if not (group ~= "" and group) then
204
- error("No group provided")
209
+ error("renderGroup requires a group", 2)
205
210
  end
206
211
  local groups = self:normalizeGroups(group)
207
212
  return self:renderNames(props, self:collectByGroup(groups), self)
@@ -211,7 +216,7 @@ do
211
216
  local group = _binding.group
212
217
  local name = _binding.name
213
218
  if not (group ~= "" and group) or not (name ~= "" and name) then
214
- error("Invalid renderGroupByName")
219
+ error("Invalid renderGroupByName call", 2)
215
220
  end
216
221
  local groups = self:normalizeGroups(group)
217
222
  return self:renderNames(props, self:collectByGroup(groups, function(n)
@@ -223,7 +228,7 @@ do
223
228
  local group = _binding.group
224
229
  local names = _binding.names
225
230
  if not (group ~= "" and group) or not names then
226
- error("Invalid renderGroupByNames")
231
+ error("Invalid renderGroupByNames call", 2)
227
232
  end
228
233
  local groups = self:normalizeGroups(group)
229
234
  return self:renderNames(props, self:collectByGroup(groups, function(n)
@@ -6,36 +6,43 @@ 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 _result = entryApp
10
- if _result ~= nil then
11
- _result = _result.rules
12
- if _result ~= nil then
13
- _result = _result.exclusiveGroup
9
+ local _group = entryApp
10
+ if _group ~= nil then
11
+ _group = _group.rules
12
+ if _group ~= nil then
13
+ _group = _group.exclusiveGroup
14
14
  end
15
15
  end
16
- if not (_result ~= "" and _result) then
16
+ local group = _group
17
+ if not (group ~= "" and group) then
17
18
  return nil
18
19
  end
19
- local group = entryApp.rules.exclusiveGroup
20
- local entrySource = forge:getSource(entry)()
21
- if not entrySource then
20
+ local entryVisible = forge:getSource(entry)()
21
+ if not entryVisible then
22
22
  return nil
23
23
  end
24
+ forge.debug:logTag("rules", entry, "Exclusive group activated", group)
24
25
  -- ▼ ReadonlyMap.forEach ▼
25
26
  local _callback = function(app, name)
26
27
  if name == entry then
27
28
  return nil
28
29
  end
29
- local _result_1 = app.rules
30
- if _result_1 ~= nil then
31
- _result_1 = _result_1.exclusiveGroup
30
+ local _result = app.rules
31
+ if _result ~= nil then
32
+ _result = _result.exclusiveGroup
32
33
  end
33
- if _result_1 ~= group then
34
+ if _result ~= group then
34
35
  return nil
35
36
  end
36
- if forge:getSource(name)() then
37
- forge:close(name, false)
37
+ local visible = forge:getSource(name)()
38
+ if not visible then
39
+ return nil
38
40
  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)
39
46
  end
40
47
  for _k, _v in AppRegistry do
41
48
  _callback(_v, _k, AppRegistry)
@@ -25,36 +25,40 @@ do
25
25
  local _name = name
26
26
  local appClass = AppRegistry[_name]
27
27
  if not appClass then
28
- error(`Failed to get class for app: {name} for renderRules`)
28
+ error(`renderRules: App "{name}" not registered`, 2)
29
29
  end
30
- local _result = appClass.rules
31
- if _result ~= nil then
32
- _result = _result.parent
30
+ local rules = appClass.rules
31
+ if not rules then
32
+ return nil
33
33
  end
34
- local _condition = _result
34
+ -- Parent Anchor
35
+ local _condition = rules.parent
35
36
  if _condition ~= "" and _condition then
36
- _condition = not appClass.rules.detach
37
+ _condition = not rules.detach
37
38
  end
38
39
  if _condition ~= "" and _condition then
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
40
+ self.debug:logTag("rules", name, "Applying parent anchor", {
41
+ parent = rules.parent,
42
+ })
43
+ self:anchor(name, rules.parent, props)
44
44
  end
45
- if _result_1 ~= 0 and _result_1 == _result_1 and _result_1 then
46
- self:index(name, appClass.rules.index)
45
+ -- Index
46
+ if rules.index ~= nil then
47
+ self.debug:logTag("rules", name, "Applying ZIndex", rules.index)
48
+ self:index(name, rules.index)
47
49
  end
48
50
  end
49
51
  function Rules:checkRules(name)
50
52
  local _processing = self.processing
51
53
  local _name = name
52
54
  if _processing[_name] ~= nil then
55
+ self.debug:logTag("rules", name, "Skipped rule processing (cycle detected)")
53
56
  return nil
54
57
  end
55
58
  local _processing_1 = self.processing
56
59
  local _name_1 = name
57
60
  _processing_1[_name_1] = true
61
+ self.debug:logTag("rules", name, "Evaluating rules")
58
62
  TS.try(function()
59
63
  ParentRule(name, self)
60
64
  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 entrySource = forge:getSource(entry)()
7
+ local entryVisible = forge:getSource(entry)()
8
8
  -- ▼ ReadonlyMap.forEach ▼
9
9
  local _callback = function(app, name)
10
10
  local rules = app.rules
@@ -14,10 +14,14 @@ local function ParentRule(entry, forge)
14
14
  if name == entry then
15
15
  return nil
16
16
  end
17
- local childSource = forge:getSource(name)()
18
- if not entrySource and childSource then
19
- forge:close(name, false)
17
+ local childVisible = forge:getSource(name)()
18
+ if entryVisible or not childVisible then
19
+ return nil
20
20
  end
21
+ forge.debug:logTag("rules", entry, "Closing child app (parent closed)", {
22
+ child = name,
23
+ })
24
+ forge:close(name, false)
21
25
  end
22
26
  for _k, _v in AppRegistry do
23
27
  _callback(_v, _k, AppRegistry)
@@ -1,7 +1,7 @@
1
1
  declare const Contexts: {
2
2
  readonly App: import("@rbxts/vide").Context<{
3
3
  forge: import(".").default;
4
- px: typeof import("@rbxts/loners-pretty-vide-utils").px;
4
+ px: typeof import("./hooks/usePx").px;
5
5
  } | undefined>;
6
6
  };
7
7
  export default Contexts;
@@ -0,0 +1,18 @@
1
+ export type DebugTag = "render" | "rules" | "state" | "px" | "lifecycle";
2
+ type LogFn = (level: "DEBUG" | "PERF", message: string, data?: unknown, traceback?: string) => void;
3
+ export default class Debugger {
4
+ private readonly log;
5
+ private timers;
6
+ private enabled;
7
+ private allEnabled;
8
+ constructor(log: LogFn);
9
+ enable(tag: DebugTag): void;
10
+ disable(tag: DebugTag): void;
11
+ enableAll(): void;
12
+ disableAll(): void;
13
+ isEnabled(tag: DebugTag): boolean;
14
+ logTag(tag: DebugTag, app: AppNames, message: string, data?: unknown): void;
15
+ time(tag: DebugTag, app: AppNames): void;
16
+ timeEnd(tag: DebugTag, app: AppNames): void;
17
+ }
18
+ export {};
@@ -0,0 +1,89 @@
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.timers = {}
20
+ self.enabled = {}
21
+ self.allEnabled = false
22
+ end
23
+ function Debugger:enable(tag)
24
+ local _enabled = self.enabled
25
+ local _tag = tag
26
+ _enabled[_tag] = true
27
+ end
28
+ function Debugger:disable(tag)
29
+ local _enabled = self.enabled
30
+ local _tag = tag
31
+ _enabled[_tag] = nil
32
+ end
33
+ function Debugger:enableAll()
34
+ self.allEnabled = true
35
+ end
36
+ function Debugger:disableAll()
37
+ self.allEnabled = false
38
+ table.clear(self.enabled)
39
+ end
40
+ function Debugger:isEnabled(tag)
41
+ local _condition = self.allEnabled
42
+ if not _condition then
43
+ local _enabled = self.enabled
44
+ local _tag = tag
45
+ _condition = _enabled[_tag] ~= nil
46
+ end
47
+ return _condition
48
+ end
49
+ function Debugger:logTag(tag, app, message, data)
50
+ if not RunService:IsStudio() then
51
+ return nil
52
+ end
53
+ if not self:isEnabled(tag) then
54
+ return nil
55
+ end
56
+ self.log("DEBUG", `[{tag}][{app}] {message}`, data, debug.traceback(nil, 3))
57
+ end
58
+ function Debugger:time(tag, app)
59
+ if not RunService:IsStudio() then
60
+ return nil
61
+ end
62
+ if not self:isEnabled(tag) then
63
+ return nil
64
+ end
65
+ local _timers = self.timers
66
+ local _arg0 = `{tag}:{app}`
67
+ local _arg1 = os.clock()
68
+ _timers[_arg0] = _arg1
69
+ end
70
+ function Debugger:timeEnd(tag, app)
71
+ if not RunService:IsStudio() then
72
+ return nil
73
+ end
74
+ if not self:isEnabled(tag) then
75
+ return nil
76
+ end
77
+ local key = `{tag}:{app}`
78
+ local start = self.timers[key]
79
+ if start == nil then
80
+ return nil
81
+ end
82
+ self.timers[key] = nil
83
+ local elapsed = os.clock() - start
84
+ self.log("PERF", `[{tag}][{app}] {string.format("%.3fms", elapsed * 1000)}`)
85
+ end
86
+ end
87
+ return {
88
+ default = Debugger,
89
+ }
@@ -0,0 +1,3 @@
1
+ import Debugger from "./debugger";
2
+ import Logger from "./logger";
3
+ export { Debugger, Logger };