Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/adorneeboundingbox/src/Shared/AdorneeBoundingBox.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
--!strict
--[=[
This utility class can observe the bounding box (size + position) of any Roblox instance effectively.
It handles anchored + unanchored instances, and tries to do it in a performance friendly way.
@class AdorneeBoundingBox
]=]

Expand Down Expand Up @@ -31,6 +34,9 @@ export type AdorneeBoundingBox =
))
& BaseObject.BaseObject

--[=[
Construct a new AdorneeBoundingBox
]=]
function AdorneeBoundingBox.new(initialAdornee: Instance?): AdorneeBoundingBox
local self = setmetatable(BaseObject.new() :: any, AdorneeBoundingBox)

Expand All @@ -54,6 +60,9 @@ function AdorneeBoundingBox.new(initialAdornee: Instance?): AdorneeBoundingBox
return self
end

--[=[
Sets the Roblox instance that we're going to observe the bounding box of
]=]
function AdorneeBoundingBox.SetAdornee(self: AdorneeBoundingBox, adornee: Instance?): () -> ()
assert(typeof(adornee) == "Instance" or adornee == nil, "Bad adornee")

Expand Down
47 changes: 30 additions & 17 deletions src/adorneeboundingbox/src/Shared/AdorneeModelBoundingBox.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
--!nonstrict
--!strict
--[=[
@class AdorneeModelBoundingBox
]=]
Expand All @@ -19,13 +19,26 @@ local AdorneeModelBoundingBox = setmetatable({}, BaseObject)
AdorneeModelBoundingBox.ClassName = "AdorneeModelBoundingBox"
AdorneeModelBoundingBox.__index = AdorneeModelBoundingBox

function AdorneeModelBoundingBox.new(model: Model)
local self = setmetatable(BaseObject.new(model), AdorneeModelBoundingBox)
export type AdorneeModelBoundingBox =
typeof(setmetatable(
{} :: {
_obj: Model,
_bbCFrame: ValueObject.ValueObject<CFrame?>,
_bbSize: ValueObject.ValueObject<Vector3?>,
_isDirty: ValueObject.ValueObject<boolean>,
_unanchoredPartsSet: ObservableSet.ObservableSet<BasePart>,
},
{} :: typeof({ __index = AdorneeModelBoundingBox })
))
& BaseObject.BaseObject

function AdorneeModelBoundingBox.new(model: Model): AdorneeModelBoundingBox
local self: AdorneeModelBoundingBox = setmetatable(BaseObject.new(model) :: any, AdorneeModelBoundingBox)

self._bbCFrame = self._maid:Add(ValueObject.new(nil))
self._bbSize = self._maid:Add(ValueObject.new(Vector3.zero, "Vector3"))
self._bbSize = self._maid:Add(ValueObject.new(nil))
self._isDirty = self._maid:Add(ValueObject.new(false, "boolean"))
self._unanchoredPartsSet = self._maid:Add(ObservableSet.new(false))
self._unanchoredPartsSet = self._maid:Add(ObservableSet.new())

self._maid:GiveTask(RxInstanceUtils.observeDescendantsBrio(self._obj, function(part)
return part:IsA("BasePart")
Expand All @@ -36,7 +49,7 @@ function AdorneeModelBoundingBox.new(model: Model)

local maid, part = brio:ToMaidAndValue()

self:_handlePart(maid, part)
self:_handlePart(maid, part :: BasePart)
end))

self._maid:GiveTask(self:_observeBasisChanged():Subscribe(function()
Expand All @@ -48,8 +61,8 @@ function AdorneeModelBoundingBox.new(model: Model)
:Pipe({
Rx.where(function(value)
return value
end),
Rx.throttleDefer(),
end) :: any,
Rx.throttleDefer() :: any,
})
:Subscribe(function()
debug.profilebegin("modelboundingbox")
Expand All @@ -66,8 +79,8 @@ function AdorneeModelBoundingBox.new(model: Model)
:Pipe({
Rx.map(function(value)
return value > 0
end),
Rx.distinct(),
end) :: any,
Rx.distinct() :: any,
})
:Subscribe(function(hasUnanchoredParts)
if hasUnanchoredParts then
Expand All @@ -80,7 +93,7 @@ function AdorneeModelBoundingBox.new(model: Model)
return self
end

function AdorneeModelBoundingBox:_setupUnanchoredLoop()
function AdorneeModelBoundingBox._setupUnanchoredLoop(self: AdorneeModelBoundingBox): Maid.Maid
local maid = Maid.new()

-- Paranoid
Expand All @@ -91,7 +104,7 @@ function AdorneeModelBoundingBox:_setupUnanchoredLoop()
return maid
end

function AdorneeModelBoundingBox:_handlePart(topMaid, part: BasePart)
function AdorneeModelBoundingBox._handlePart(self: AdorneeModelBoundingBox, topMaid, part: BasePart)
topMaid:GiveTask(RxInstanceUtils.observePropertyBrio(part, "Anchored", function(isAnchored)
return not isAnchored
end):Subscribe(function(brio)
Expand All @@ -112,23 +125,23 @@ function AdorneeModelBoundingBox:_handlePart(topMaid, part: BasePart)
self._isDirty.Value = true
end

function AdorneeModelBoundingBox:_observeBasisChanged()
function AdorneeModelBoundingBox._observeBasisChanged(self: AdorneeModelBoundingBox): Observable.Observable<()>
return RxInstanceUtils.observeProperty(self._obj, "PrimaryPart"):Pipe({
Rx.switchMap(function(primaryPart)
if primaryPart then
return RxInstanceUtils.observeProperty(primaryPart, "PivotOffset")
else
return RxInstanceUtils.observeProperty(self._obj, "WorldPivot")
end
end),
})
end) :: any,
}) :: any
end

function AdorneeModelBoundingBox:ObserveCFrame(): Observable.Observable<CFrame>
function AdorneeModelBoundingBox.ObserveCFrame(self: AdorneeModelBoundingBox): Observable.Observable<CFrame?>
return self._bbCFrame:Observe()
end

function AdorneeModelBoundingBox:ObserveSize(): Observable.Observable<Vector3>
function AdorneeModelBoundingBox.ObserveSize(self: AdorneeModelBoundingBox): Observable.Observable<Vector3?>
return self._bbSize:Observe()
end

Expand Down
32 changes: 20 additions & 12 deletions src/adorneeboundingbox/src/Shared/AdorneePartBoundingBox.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
--!nonstrict
--!strict
--[=[
@class AdorneePartBoundingBox
]=]
Expand All @@ -8,21 +8,32 @@ local require = require(script.Parent.loader).load(script)
local RunService = game:GetService("RunService")

local BaseObject = require("BaseObject")
local Maid = require("Maid")
local Observable = require("Observable")
local RxInstanceUtils = require("RxInstanceUtils")
local ValueObject = require("ValueObject")

local AdorneePartBoundingBox = setmetatable({}, BaseObject)
AdorneePartBoundingBox.ClassName = "AdorneePartBoundingBox"
AdorneePartBoundingBox.__index = AdorneePartBoundingBox

function AdorneePartBoundingBox.new(part: BasePart)
export type AdorneePartBoundingBox =
typeof(setmetatable(
{} :: {
_obj: BasePart,
_bbCFrame: ValueObject.ValueObject<CFrame?>,
_bbSize: ValueObject.ValueObject<Vector3?>,
},
{} :: typeof({ __index = AdorneePartBoundingBox })
))
& BaseObject.BaseObject

function AdorneePartBoundingBox.new(part: BasePart): AdorneePartBoundingBox
assert(typeof(part) == "Instance" and part:IsA("BasePart"), "Bad part")

local self = setmetatable(BaseObject.new(part), AdorneePartBoundingBox)
local self: AdorneePartBoundingBox = setmetatable(BaseObject.new(part) :: any, AdorneePartBoundingBox)

self._bbCFrame = self._maid:Add(ValueObject.new(nil))
self._bbSize = self._maid:Add(ValueObject.new(Vector3.zero, "Vector3"))
self._bbCFrame = self._maid:Add(ValueObject.new(self._obj.CFrame, "CFrame") :: any)
self._bbSize = self._maid:Add(ValueObject.new(self._obj.Size, "Vector3") :: any)

self._maid:GiveTask(RxInstanceUtils.observePropertyBrio(self._obj, "Anchored", function(anchored)
return not anchored
Expand All @@ -35,9 +46,6 @@ function AdorneePartBoundingBox.new(part: BasePart)
self:_setupUnanchoredLoop(maid)
end))

self._bbSize.Value = self._obj.Size
self._bbCFrame.Value = self._obj.CFrame

self._maid:GiveTask(self._obj:GetPropertyChangedSignal("Size"):Connect(function()
self._bbSize.Value = self._obj.Size
end))
Expand All @@ -48,7 +56,7 @@ function AdorneePartBoundingBox.new(part: BasePart)
return self
end

function AdorneePartBoundingBox:_setupUnanchoredLoop(maid)
function AdorneePartBoundingBox._setupUnanchoredLoop(self: AdorneePartBoundingBox, maid: Maid.Maid): ()
-- Paranoid
maid:GiveTask(RunService.Heartbeat:Connect(function()
debug.profilebegin("adorneeboundingbox")
Expand All @@ -57,11 +65,11 @@ function AdorneePartBoundingBox:_setupUnanchoredLoop(maid)
end))
end

function AdorneePartBoundingBox:ObserveCFrame(): Observable.Observable<CFrame>
function AdorneePartBoundingBox.ObserveCFrame(self: AdorneePartBoundingBox): Observable.Observable<CFrame?>
return self._bbCFrame:Observe()
end

function AdorneePartBoundingBox:ObserveSize(): Observable.Observable<Vector3>
function AdorneePartBoundingBox.ObserveSize(self: AdorneePartBoundingBox): Observable.Observable<Vector3?>
return self._bbSize:Observe()
end

Expand Down
6 changes: 6 additions & 0 deletions src/adorneeboundingbox/src/Shared/RxPartBoundingBoxUtils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ local RxInstanceUtils = require("RxInstanceUtils")

local RxPartBoundingBoxUtils = {}

--[=[
Observes the CFrame of a part
@param part BasePart
@return Observable<CFrame>
]=]
function RxPartBoundingBoxUtils.observePartCFrame(part: BasePart): Observable.Observable<CFrame>
assert(typeof(part) == "Instance" and part:IsA("BasePart"), "Bad part")

Expand Down
22 changes: 8 additions & 14 deletions src/settings/src/Server/Player/PlayerSettings.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export type PlayerSettings =
))
& PlayerSettingsBase.PlayerSettingsBase

export type SettingsMap = { [string]: any }

function PlayerSettings.new(folder: Folder, serviceBag: ServiceBag.ServiceBag): PlayerSettings
local self: PlayerSettings = setmetatable(PlayerSettingsBase.new(folder, serviceBag) :: any, PlayerSettings)

Expand Down Expand Up @@ -75,49 +77,41 @@ function PlayerSettings.EnsureInitialized<T>(self: PlayerSettings, settingName:
end
end

function PlayerSettings._setupRemoting(self: PlayerSettings)
function PlayerSettings._setupRemoting(self: PlayerSettings): ()
self._remoting = self._maid:Add(Remoting.new(self._obj, "PlayerSettings", Remoting.Realms.SERVER))

self._maid:Add(self._remoting.RequestUpdateSettings:Bind(function(player, settingsMap)
self._maid:Add(self._remoting.RequestUpdateSettings:Bind(function(player: Player, settingsMap: SettingsMap)
assert(self:GetPlayer() == player, "Bad player")

return self:_setSettingsMap(settingsMap)
end))
end

function PlayerSettings._setSettingsMap(self: PlayerSettings, settingsMap)
function PlayerSettings._setSettingsMap(self: PlayerSettings, settingsMap: SettingsMap): ()
assert(type(settingsMap) == "table", "Bad settingsMap")

for settingName, value in settingsMap do
assert(type(settingName) == "string", "Bad key")

-- Avoid even letting these be set.
if not DataStoreStringUtils.isValidUTF8(settingName) then
warn("[PlayerSettings] - Bad UTF8 settingName. Skipping setting.")
warn(`[PlayerSettings] - Bad UTF8 settingName. Skipping setting.`)
continue
end

local attributeName = PlayerSettingsUtils.getAttributeName(settingName)

if self._obj:GetAttribute(attributeName) == nil then
warn(
string.format(
"[PlayerSettings] - Cannot set setting %q on attribute that is not defined on the server.",
attributeName
)
`[PlayerSettings] - Cannot set setting {attributeName} on attribute that is not defined on the server. Be sure to initialize settings on both server + client.`
)
continue
end

-- Paranoid UTF8 check. Avoid letting this value be set.
if type(value) == "string" then
if not DataStoreStringUtils.isValidUTF8(value) then
warn(
string.format(
"[PlayerSettings] - Bad UTF8 value setting value for %q. Skipping setting.",
settingName
)
)
warn(`[PlayerSettings] - Bad UTF8 value setting value for {settingName}. Skipping setting.`)
continue
end
end
Expand Down
Loading