@rbxts/vfx-forge 2.2.2-ts.1
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/LICENSE +82 -0
- package/README.md +39 -0
- package/out/forge-vfx/effects/beam.luau +312 -0
- package/out/forge-vfx/effects/bezier.luau +392 -0
- package/out/forge-vfx/effects/camera_shake.luau +200 -0
- package/out/forge-vfx/effects/lightning.luau +1183 -0
- package/out/forge-vfx/effects/mesh.luau +466 -0
- package/out/forge-vfx/effects/particle.luau +64 -0
- package/out/forge-vfx/effects/randomizer.luau +110 -0
- package/out/forge-vfx/effects/screen.luau +61 -0
- package/out/forge-vfx/effects/shockwave_debris.luau +277 -0
- package/out/forge-vfx/effects/shockwave_line.luau +356 -0
- package/out/forge-vfx/effects/shockwave_ring.luau +252 -0
- package/out/forge-vfx/effects/sound.luau +311 -0
- package/out/forge-vfx/effects/spin.luau +88 -0
- package/out/forge-vfx/effects/tweener.luau +122 -0
- package/out/forge-vfx/emitters.luau +387 -0
- package/out/forge-vfx/index.d.ts +356 -0
- package/out/forge-vfx/init.luau +279 -0
- package/out/forge-vfx/mod/attributes.luau +227 -0
- package/out/forge-vfx/mod/color/Oklab.luau +93 -0
- package/out/forge-vfx/mod/color/sRGB.luau +71 -0
- package/out/forge-vfx/mod/common/bezier.luau +372 -0
- package/out/forge-vfx/mod/common/flipbook.luau +102 -0
- package/out/forge-vfx/mod/lerp.luau +210 -0
- package/out/forge-vfx/mod/logger.luau +20 -0
- package/out/forge-vfx/mod/shape.luau +207 -0
- package/out/forge-vfx/mod/tween.luau +161 -0
- package/out/forge-vfx/mod/utility.luau +707 -0
- package/out/forge-vfx/obj/Bezier.luau +268 -0
- package/out/forge-vfx/obj/ObjectCache.luau +289 -0
- package/out/forge-vfx/services/caches.luau +62 -0
- package/out/forge-vfx/services/effects.luau +234 -0
- package/out/forge-vfx/services/enabled_effects.luau +120 -0
- package/out/forge-vfx/services/texture_loader.luau +174 -0
- package/out/forge-vfx/types.luau +43 -0
- package/out/index.d.ts +3 -0
- package/out/init.luau +5 -0
- package/out/shake.d.ts +2 -0
- package/out/shake.luau +6 -0
- package/out/tsconfig.tsbuildinfo +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
--!native
|
|
2
|
+
--!nocheck
|
|
3
|
+
--!optimize 2
|
|
4
|
+
type n = vector | number
|
|
5
|
+
|
|
6
|
+
local EASE_LUT_SIZE = 64
|
|
7
|
+
|
|
8
|
+
local NEWTON_MIN_TOL = 0.001
|
|
9
|
+
local NEWTON_MAX_ITER = 5
|
|
10
|
+
|
|
11
|
+
local function cubicBezier(p0: n, p1: n, p2: n, p3: n, t: n): n
|
|
12
|
+
-- stylua: ignore
|
|
13
|
+
return (1-t)^3*p0+3*(1-t)^2*t*p1+3*(1-t)*t^2*p2+t^3*p3
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
local function cubicBezierDerivative(p0: n, p1: n, p2: n, p3: n, t: n): n
|
|
17
|
+
-- stylua: ignore
|
|
18
|
+
return 3*(1-t)^2*(p1-p0)+6*(1-t)*t*(p2-p1)+3*t^2*(p3-p2)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
local bezier = {}
|
|
22
|
+
bezier.__index = bezier
|
|
23
|
+
|
|
24
|
+
export type Bezier = typeof(bezier) & {
|
|
25
|
+
points: { vector },
|
|
26
|
+
cumulative_lengths: { number },
|
|
27
|
+
ease_lut: { vector }?,
|
|
28
|
+
|
|
29
|
+
length: number,
|
|
30
|
+
accuracy: number,
|
|
31
|
+
point_count: number,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function bezier.new(points: { vector }, samplingAccuracy: number?)
|
|
35
|
+
local self = setmetatable({}, bezier)
|
|
36
|
+
|
|
37
|
+
self.points = points
|
|
38
|
+
self.accuracy = samplingAccuracy or 20
|
|
39
|
+
|
|
40
|
+
self.point_count = 0
|
|
41
|
+
self.cumulative_lengths = {}
|
|
42
|
+
|
|
43
|
+
self:_recalculate()
|
|
44
|
+
|
|
45
|
+
return self
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
function bezier.setPoints(self: Bezier, points: { vector })
|
|
49
|
+
self.points = points
|
|
50
|
+
self.ease_lut = nil
|
|
51
|
+
|
|
52
|
+
self:_recalculate()
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
function bezier.getSegmentPoints(self: Bezier, index: number)
|
|
56
|
+
if index < 1 or index > self.point_count - 1 then
|
|
57
|
+
if index <= 0 then
|
|
58
|
+
return vector.zero, vector.zero, vector.zero, vector.zero
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
error("attempt to get a non-existent segment at index " .. index)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
local offset = (index - 1) * 4 - math.max(index - 2, 0)
|
|
65
|
+
local start = math.max(offset, 1)
|
|
66
|
+
|
|
67
|
+
local p0 = self.points[start]
|
|
68
|
+
local p1 = self.points[start + 1]
|
|
69
|
+
local p2 = self.points[start + 2]
|
|
70
|
+
local p3 = self.points[start + 3]
|
|
71
|
+
|
|
72
|
+
return p0, p1, p2, p3
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
function bezier.forSample(self: Bezier, size: number, callback: (pos: vector, i: number) -> ())
|
|
76
|
+
local amount = self.length // size
|
|
77
|
+
|
|
78
|
+
if amount == 0 then
|
|
79
|
+
return
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
for i = 0, amount do
|
|
83
|
+
local t = i / amount
|
|
84
|
+
callback(self:getPositionArcSpace(t), i)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
function bezier.getPosition(self: Bezier, t: number)
|
|
89
|
+
local i, u = self:getSegmentIndex(t)
|
|
90
|
+
local p0, p1, p2, p3 = self:getSegmentPoints(i)
|
|
91
|
+
|
|
92
|
+
return cubicBezier(p0, p1, p2, p3, u)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
function bezier.getSegmentIndex(self: Bezier, t: number)
|
|
96
|
+
t = math.clamp(t, 0, 1)
|
|
97
|
+
|
|
98
|
+
local m = self.point_count - 1
|
|
99
|
+
local s = t * m
|
|
100
|
+
|
|
101
|
+
local i = math.min(math.floor(s) + 1, m)
|
|
102
|
+
local u = s - math.floor(s)
|
|
103
|
+
|
|
104
|
+
if t == 1 then
|
|
105
|
+
u = 1
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
return i, u
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
-- only works with beziers whose input is the X axis
|
|
112
|
+
function bezier.getEasedSegmentIndex(self: Bezier, t: number)
|
|
113
|
+
local i = self.point_count - 1
|
|
114
|
+
local u = 1
|
|
115
|
+
|
|
116
|
+
for j = 1, i do
|
|
117
|
+
local p0, _, _, p3 = self:getSegmentPoints(j)
|
|
118
|
+
|
|
119
|
+
local start = p0.x
|
|
120
|
+
local stop = p3.x
|
|
121
|
+
|
|
122
|
+
if t >= start and t < stop then
|
|
123
|
+
i = j
|
|
124
|
+
u = (t - start) / (stop - start)
|
|
125
|
+
|
|
126
|
+
break
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
return i, u
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
function bezier._getEaseRaw(self: Bezier, t: number)
|
|
134
|
+
local i, s = self:getEasedSegmentIndex(t)
|
|
135
|
+
local p0, p1, p2, p3 = self:getSegmentPoints(i)
|
|
136
|
+
|
|
137
|
+
for i = 1, NEWTON_MAX_ITER do
|
|
138
|
+
local x = cubicBezier(p0.x, p1.x, p2.x, p3.x, s)
|
|
139
|
+
local dx = cubicBezierDerivative(p0.x, p1.x, p2.x, p3.x, s)
|
|
140
|
+
|
|
141
|
+
if dx == 0 then
|
|
142
|
+
break
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
local ds = (x - t) / dx
|
|
146
|
+
s = s - ds
|
|
147
|
+
|
|
148
|
+
if s < 0 then
|
|
149
|
+
s = 0
|
|
150
|
+
elseif s > 1 then
|
|
151
|
+
s = 1
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
if math.abs(ds) < NEWTON_MIN_TOL then
|
|
155
|
+
break
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
return cubicBezier(p0, p1, p2, p3, s)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
function bezier._buildEaseLUT(self: Bezier)
|
|
163
|
+
local lut = table.create(EASE_LUT_SIZE + 1)
|
|
164
|
+
|
|
165
|
+
for i = 0, EASE_LUT_SIZE do
|
|
166
|
+
local t = i / EASE_LUT_SIZE
|
|
167
|
+
lut[i] = self:_getEaseRaw(t)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
self.ease_lut = lut
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
function bezier.getEase(self: Bezier, t: number)
|
|
174
|
+
local lut = self.ease_lut
|
|
175
|
+
|
|
176
|
+
if not lut then
|
|
177
|
+
self:_buildEaseLUT()
|
|
178
|
+
lut = self.ease_lut
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
t = math.clamp(t, 0, 1)
|
|
182
|
+
|
|
183
|
+
local idx = t * EASE_LUT_SIZE
|
|
184
|
+
|
|
185
|
+
local lo = math.floor(idx)
|
|
186
|
+
local hi = math.min(lo + 1, EASE_LUT_SIZE)
|
|
187
|
+
|
|
188
|
+
local frac = idx - lo
|
|
189
|
+
|
|
190
|
+
local loVal = lut[lo]
|
|
191
|
+
local hiVal = lut[hi]
|
|
192
|
+
|
|
193
|
+
if not loVal or not hiVal then
|
|
194
|
+
return self:_getEaseRaw(t)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
return loVal:Lerp(hiVal, frac)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
function bezier.getPositionArcSpace(self: Bezier, t: number)
|
|
201
|
+
if self.length <= 0 then
|
|
202
|
+
return self.points[1] or vector.zero
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
t = math.clamp(t, 0, 1)
|
|
206
|
+
|
|
207
|
+
local targetLength = t * self.cumulative_lengths[self.accuracy + 1]
|
|
208
|
+
local low, high, index = 1,(self.accuracy + 1)
|
|
209
|
+
|
|
210
|
+
while low < high do
|
|
211
|
+
index = low + (high - low) // 2
|
|
212
|
+
|
|
213
|
+
if self.cumulative_lengths[index] < targetLength then
|
|
214
|
+
low = index + 1
|
|
215
|
+
else
|
|
216
|
+
high = index
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
if self.cumulative_lengths[index] > targetLength and index > 1 then
|
|
221
|
+
index -= 1
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
local lengthBefore = self.cumulative_lengths[index]
|
|
225
|
+
|
|
226
|
+
if lengthBefore == targetLength then
|
|
227
|
+
return self:getPosition((index - 1) / self.accuracy)
|
|
228
|
+
else
|
|
229
|
+
return self:getPosition(
|
|
230
|
+
((index - 1) + (targetLength - lengthBefore) / (self.cumulative_lengths[index + 1] - lengthBefore))
|
|
231
|
+
/ self.accuracy
|
|
232
|
+
)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
function bezier._recalculate(self: Bezier)
|
|
237
|
+
table.clear(self.cumulative_lengths)
|
|
238
|
+
table.insert(self.cumulative_lengths, 0)
|
|
239
|
+
|
|
240
|
+
local count = math.ceil(#self.points / 3)
|
|
241
|
+
|
|
242
|
+
self.point_count = count
|
|
243
|
+
|
|
244
|
+
local total = 0
|
|
245
|
+
|
|
246
|
+
local prev: vector
|
|
247
|
+
|
|
248
|
+
for j = 1, self.accuracy do
|
|
249
|
+
local p = self:getPosition(j / self.accuracy)
|
|
250
|
+
|
|
251
|
+
if not prev then
|
|
252
|
+
prev = self:getPosition(0)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
total += vector.magnitude(p - prev)
|
|
256
|
+
prev = p
|
|
257
|
+
|
|
258
|
+
table.insert(self.cumulative_lengths, total)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
self.length = total
|
|
262
|
+
|
|
263
|
+
for i, len in self.cumulative_lengths do
|
|
264
|
+
self.cumulative_lengths[i] = len / total
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
return bezier
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
local utility = require(script.Parent.Parent.mod.utility)
|
|
2
|
+
|
|
3
|
+
local RECYCLE_INTERVAL = 15
|
|
4
|
+
|
|
5
|
+
local cache = {}
|
|
6
|
+
cache.__index = cache
|
|
7
|
+
|
|
8
|
+
type item = {
|
|
9
|
+
key: any,
|
|
10
|
+
value: Instance,
|
|
11
|
+
|
|
12
|
+
added: number,
|
|
13
|
+
dependents: number,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type params = {
|
|
17
|
+
size: number?,
|
|
18
|
+
excess_lifetime: number?,
|
|
19
|
+
on_free: ((item) -> ())?,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ObjectCache = typeof(cache) & {
|
|
23
|
+
ref: Instance,
|
|
24
|
+
parent: Instance?,
|
|
25
|
+
|
|
26
|
+
params: params,
|
|
27
|
+
|
|
28
|
+
amount: number,
|
|
29
|
+
restore_amount: number,
|
|
30
|
+
|
|
31
|
+
part_mode: boolean,
|
|
32
|
+
|
|
33
|
+
item_map: { [any]: item },
|
|
34
|
+
unused: { item },
|
|
35
|
+
|
|
36
|
+
scope: { any },
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
local function reconcile(a, b)
|
|
40
|
+
for k, v in b do
|
|
41
|
+
if a[k] == nil then
|
|
42
|
+
a[k] = v
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
return a
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
local moveParts: { BasePart } = table.create(10_000)
|
|
50
|
+
local movePartCFrames: { CFrame } = table.create(10_000)
|
|
51
|
+
|
|
52
|
+
local partUpdateScheduled = false
|
|
53
|
+
|
|
54
|
+
local bulkPartMoveThread = coroutine.create(function()
|
|
55
|
+
while true do
|
|
56
|
+
workspace:BulkMoveTo(moveParts, movePartCFrames, Enum.BulkMoveMode.FireCFrameChanged)
|
|
57
|
+
|
|
58
|
+
table.clear(moveParts)
|
|
59
|
+
table.clear(movePartCFrames)
|
|
60
|
+
|
|
61
|
+
partUpdateScheduled = false
|
|
62
|
+
|
|
63
|
+
coroutine.yield()
|
|
64
|
+
end
|
|
65
|
+
end)
|
|
66
|
+
|
|
67
|
+
function cache.new(ref: Instance, parent: Instance?, params: params?)
|
|
68
|
+
local self = setmetatable({}, cache)
|
|
69
|
+
|
|
70
|
+
self.ref = ref
|
|
71
|
+
self.parent = parent
|
|
72
|
+
|
|
73
|
+
self.amount = 0
|
|
74
|
+
self.restore_amount = 0
|
|
75
|
+
|
|
76
|
+
self.params = reconcile(params or {}, {
|
|
77
|
+
size = 100,
|
|
78
|
+
excess_lifetime = 30,
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
self.scope = { ref, parent }
|
|
82
|
+
self.unused = table.create(self.params.size)
|
|
83
|
+
self.item_map = {}
|
|
84
|
+
|
|
85
|
+
self.part_mode = ref:IsA("BasePart")
|
|
86
|
+
|
|
87
|
+
for _ = 1, self.params.size do
|
|
88
|
+
self:_add()
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
table.insert(
|
|
92
|
+
self.scope,
|
|
93
|
+
task.spawn(function()
|
|
94
|
+
while task.wait(RECYCLE_INTERVAL) do
|
|
95
|
+
if self.restore_amount > 0 then
|
|
96
|
+
for _ = 1, self.restore_amount do
|
|
97
|
+
self:_add()
|
|
98
|
+
self.restore_amount -= 1
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
if self.amount <= self.params.size then
|
|
103
|
+
continue
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
local offset = 0
|
|
107
|
+
|
|
108
|
+
for i = 1, #self.unused do
|
|
109
|
+
if self.amount <= self.params.size then
|
|
110
|
+
break
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
i -= offset
|
|
114
|
+
|
|
115
|
+
local item = self.unused[i]
|
|
116
|
+
|
|
117
|
+
if item.dependents ~= 0 or (os.clock() - item.added) > self.params.excess_lifetime then
|
|
118
|
+
continue
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
item:destroy()
|
|
122
|
+
|
|
123
|
+
table.remove(self.unused, i)
|
|
124
|
+
|
|
125
|
+
self.item_map[item.key] = nil
|
|
126
|
+
self.amount -= 1
|
|
127
|
+
|
|
128
|
+
offset += 1
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return self
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
function cache._add(self: ObjectCache, key: any?, excess: boolean?)
|
|
138
|
+
local obj = self.ref:Clone()
|
|
139
|
+
obj.Archivable = false
|
|
140
|
+
obj.Parent = self.parent
|
|
141
|
+
|
|
142
|
+
local item = {
|
|
143
|
+
key = key,
|
|
144
|
+
value = obj,
|
|
145
|
+
|
|
146
|
+
added = os.clock(),
|
|
147
|
+
dependents = 1,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
local ignoreDestroy = false
|
|
151
|
+
|
|
152
|
+
function item:destroy()
|
|
153
|
+
ignoreDestroy = true
|
|
154
|
+
obj:Destroy()
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
self.amount += 1
|
|
158
|
+
|
|
159
|
+
obj.Destroying:Connect(function()
|
|
160
|
+
if ignoreDestroy then
|
|
161
|
+
return
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
local index = table.find(self.unused, item)
|
|
165
|
+
|
|
166
|
+
if index then
|
|
167
|
+
table.remove(self.unused, index)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
if item.key then
|
|
171
|
+
self.item_map[item.key] = nil
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
self.amount -= 1
|
|
175
|
+
self.restore_amount += 1
|
|
176
|
+
end)
|
|
177
|
+
|
|
178
|
+
if key then
|
|
179
|
+
self.item_map[key] = item
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
if not excess then
|
|
183
|
+
table.insert(self.unused, item)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
return item
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
function cache.has(self: ObjectCache, key: any)
|
|
190
|
+
return if self.item_map[key] then true else false
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
function cache.peek(self: ObjectCache, key: any)
|
|
194
|
+
return self.item_map[key]
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
function cache.get(self: ObjectCache, key: any)
|
|
198
|
+
if self:has(key) then
|
|
199
|
+
local item = self:peek(key)
|
|
200
|
+
|
|
201
|
+
if item then
|
|
202
|
+
item.dependents += 1
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
return item.value
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
local item = table.remove(self.unused)
|
|
209
|
+
|
|
210
|
+
if item then
|
|
211
|
+
item.key = key
|
|
212
|
+
item.added = os.clock()
|
|
213
|
+
|
|
214
|
+
self.item_map[key] = item
|
|
215
|
+
else
|
|
216
|
+
item = self:_add(key, true)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
if self.part_mode then
|
|
220
|
+
local meta = {}
|
|
221
|
+
|
|
222
|
+
function meta:__newindex(key, value)
|
|
223
|
+
if key == "CFrame" then
|
|
224
|
+
table.insert(moveParts, item.value)
|
|
225
|
+
table.insert(movePartCFrames, value)
|
|
226
|
+
|
|
227
|
+
if not partUpdateScheduled then
|
|
228
|
+
partUpdateScheduled = true
|
|
229
|
+
task.defer(bulkPartMoveThread)
|
|
230
|
+
end
|
|
231
|
+
else
|
|
232
|
+
item.value[key] = value
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
function meta:__index(key)
|
|
237
|
+
local v = item.value[key]
|
|
238
|
+
|
|
239
|
+
if typeof(v) == "function" then
|
|
240
|
+
return function(_, ...)
|
|
241
|
+
return v(item.value, ...)
|
|
242
|
+
end
|
|
243
|
+
else
|
|
244
|
+
return v
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
return setmetatable({
|
|
249
|
+
_getReal = function()
|
|
250
|
+
return item.value
|
|
251
|
+
end,
|
|
252
|
+
}, meta)
|
|
253
|
+
else
|
|
254
|
+
return item.value
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
function cache.free(self: ObjectCache, key: any)
|
|
259
|
+
local item = self.item_map[key]
|
|
260
|
+
|
|
261
|
+
if not item then
|
|
262
|
+
return
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
item.dependents = math.max(item.dependents - 1, 0)
|
|
266
|
+
|
|
267
|
+
if item.dependents == 0 then
|
|
268
|
+
item.added = os.clock()
|
|
269
|
+
|
|
270
|
+
if self.params.on_free then
|
|
271
|
+
task.spawn(self.params.on_free, item)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
table.insert(self.unused, item)
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
function cache.destroy(self: ObjectCache)
|
|
279
|
+
utility.cleanupScope(self.scope)
|
|
280
|
+
|
|
281
|
+
for _, item in self.item_map do
|
|
282
|
+
item.value:Destroy()
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
table.clear(self.unused)
|
|
286
|
+
table.clear(self.item_map)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
return cache
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
local ObjectCache = require(script.Parent.Parent.obj.ObjectCache)
|
|
2
|
+
|
|
3
|
+
local utility = require(script.Parent.Parent.mod.utility)
|
|
4
|
+
|
|
5
|
+
local caches = {}
|
|
6
|
+
|
|
7
|
+
function caches.init(scope: { any })
|
|
8
|
+
local shared_part_cache
|
|
9
|
+
|
|
10
|
+
do
|
|
11
|
+
local template = Instance.new("Part")
|
|
12
|
+
template.Transparency = 1
|
|
13
|
+
template.Anchored = true
|
|
14
|
+
template.CanCollide = false
|
|
15
|
+
template.CanQuery = false
|
|
16
|
+
template.Locked = true
|
|
17
|
+
|
|
18
|
+
local parent = Instance.new("Folder")
|
|
19
|
+
parent.Name = "DO_NOT_REMOVE_ForgeSharedPartCache"
|
|
20
|
+
parent.Archivable = false
|
|
21
|
+
parent.Parent = workspace.Terrain
|
|
22
|
+
|
|
23
|
+
utility.protectParent(scope, parent)
|
|
24
|
+
|
|
25
|
+
shared_part_cache = ObjectCache.new(template, parent, {
|
|
26
|
+
size = 150,
|
|
27
|
+
on_free = function(item)
|
|
28
|
+
local part = item.value
|
|
29
|
+
|
|
30
|
+
part.Transparency = 1
|
|
31
|
+
|
|
32
|
+
part.Anchored = true
|
|
33
|
+
part.CanQuery = false
|
|
34
|
+
part.CanCollide = false
|
|
35
|
+
|
|
36
|
+
part.CollisionGroup = "ForgeMouseIgnore"
|
|
37
|
+
|
|
38
|
+
part.AssemblyLinearVelocity = Vector3.zero
|
|
39
|
+
part.AssemblyAngularVelocity = Vector3.zero
|
|
40
|
+
|
|
41
|
+
part.Parent = parent
|
|
42
|
+
|
|
43
|
+
-- this is probably not very good for performance lol
|
|
44
|
+
-- but I don't think there's currently a more reasonable
|
|
45
|
+
-- way of recycling parts
|
|
46
|
+
part:ClearAllChildren()
|
|
47
|
+
end,
|
|
48
|
+
})
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
local result = {
|
|
52
|
+
shared_part = shared_part_cache,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
table.insert(scope, function()
|
|
56
|
+
shared_part_cache:destroy()
|
|
57
|
+
end)
|
|
58
|
+
|
|
59
|
+
return result
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
return caches
|