Skip to content

Commit

Permalink
Add ExpectComponent
Browse files Browse the repository at this point in the history
  • Loading branch information
tpc9000 committed Jul 19, 2022
1 parent 8c36b42 commit c3a97d7
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 30 deletions.
37 changes: 37 additions & 0 deletions build.rbxlx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
<BinaryString name="Tags"></BinaryString>
<bool name="TerrainWeldsFixed">true</bool>
<bool name="TouchesUseCollisionGroups">false</bool>
<token name="UnionsScaleNonuniformly">0</token>
<UniqueId name="UniqueId">3382962fefa0d55f02cabcb70001e0ea</UniqueId>
<OptionalCoordinateFrame name="WorldPivotData">
<CFrame>
Expand Down Expand Up @@ -6206,6 +6207,7 @@ local ERR_COMPONENT_NEW_YIELDED = "Component constructor %s yielded or threw an
local ERR_ITEM_ALREADY_DESTROYED = "Already destroyed!"
local ERR_COMPONENT_ALREADY_PRESENT = "Component %s already present on %s"
local ERR_COMPONENT_DESTROY_YIELDED = "Component destructor %s yielded or threw an error on %s"
local ERR_COMPONENT_INSTANCE_NOT_FOUND = "Expected component '%s' to exist on Instance '%s'"
local WARN_COMPONENT_LIFECYCLE_ALREDY_ENDED = "Component lifecycle ended before Initial call completed - %s on %s"

local WARN_MULTIPLE_REGISTER = "Register attempted to create duplicate component: %s\n\n%s"
Expand Down Expand Up @@ -6405,6 +6407,21 @@ function Rosyn.GetComponent<T>(Object: Instance, ComponentClass: T): T
return ComponentsForObject and ComponentsForObject[ComponentClass] or nil
end

local ExpectComponentParams = TypeGuard.Params(ValidGameObject, ValidComponentClass)
--- Asserts that a component exists on a given Instance.
function Rosyn.ExpectComponent<T>(Object: Instance, ComponentClass: T)
if (VALIDATE_PARAMS) then
ExpectComponentParams(Object, ComponentClass)
end

local Component = Rosyn.GetComponent(Object, ComponentClass)

if (Component == nil) then
error(ERR_COMPONENT_INSTANCE_NOT_FOUND:format(Rosyn.GetComponentName(ComponentClass), Object))
end

return Component
end

local AwaitComponentInitParams = TypeGuard.Params(ValidGameObject, ValidComponentClass, TypeGuard.Number():Optional())
--[[
Expand Down Expand Up @@ -7000,6 +7017,26 @@ return CheckYield]]></ProtectedString>
end)
end)

describe("Rosyn.ExpectComponent", function()
it("should throw an error where no component present", function()
local Test = MakeClass()

expect(function()
Rosyn.ExpectComponent(Workspace, Test)
end).to.throw()
end)

it("should return a component when component present", function()
local Test1 = MakeClass()
local Test2 = MakeClass()
local Inst = MakeTestInstance({"ExpectComponent1"}, Workspace)

Rosyn.Register("ExpectComponent1", {Test1, Test2})
expect(Rosyn.ExpectComponent(Inst, Test1)).to.be.ok()
expect(Rosyn.ExpectComponent(Inst, Test2)).to.be.ok()
end)
end)

describe("Rosyn.AwaitComponentInit", function()
it("should immediately return component when present", function()
local Test1 = MakeClass()
Expand Down
20 changes: 20 additions & 0 deletions src/Rosyn.spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,26 @@ return function()
end)
end)

describe("Rosyn.ExpectComponent", function()
it("should throw an error where no component present", function()
local Test = MakeClass()

expect(function()
Rosyn.ExpectComponent(Workspace, Test)
end).to.throw()
end)

it("should return a component when component present", function()
local Test1 = MakeClass()
local Test2 = MakeClass()
local Inst = MakeTestInstance({"ExpectComponent1"}, Workspace)

Rosyn.Register("ExpectComponent1", {Test1, Test2})
expect(Rosyn.ExpectComponent(Inst, Test1)).to.be.ok()
expect(Rosyn.ExpectComponent(Inst, Test2)).to.be.ok()
end)
end)

describe("Rosyn.AwaitComponentInit", function()
it("should immediately return component when present", function()
local Test1 = MakeClass()
Expand Down
58 changes: 29 additions & 29 deletions src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ local ERR_COMPONENT_NEW_YIELDED = "Component constructor %s yielded or threw an
local ERR_ITEM_ALREADY_DESTROYED = "Already destroyed!"
local ERR_COMPONENT_ALREADY_PRESENT = "Component %s already present on %s"
local ERR_COMPONENT_DESTROY_YIELDED = "Component destructor %s yielded or threw an error on %s"
local ERR_COMPONENT_INSTANCE_NOT_FOUND = "Expected component '%s' to exist on Instance '%s'"
local WARN_COMPONENT_LIFECYCLE_ALREDY_ENDED = "Component lifecycle ended before Initial call completed - %s on %s"

local WARN_MULTIPLE_REGISTER = "Register attempted to create duplicate component: %s\n\n%s"
Expand Down Expand Up @@ -70,30 +71,14 @@ local _ComponentClassInitializedEvents = {}
]]
local Rosyn = {
-- Associations between Instances, component classes, and component instances, to ensure immediate lookup

--- Map of tagged Instances as keys with values of Array<ComponentClass>
-- @usage InstanceToComponents = {Instance = {ComponentClass1 = ComponentInstance1, ComponentClass2 = ComponentInstance2, ...}, ...}
InstanceToComponents = _InstanceToComponents;
--- Map of ComponentClasses as keys with values of Array<Instance>
-- @usage ComponentClassToInstances = {ComponentClass = {Instance1 = true, Instance2 = true, ...}, ...}
ComponentClassToInstances = _ComponentClassToInstances;
--- Map of Uninitialized Component Classes as keys with values of Array<individual Class Instances>
-- @usage ComponentClassToComponents = {ComponentClass = {ComponentInstance1 = true, ComponentInstance2 = true, ...}, ...}
ComponentClassToComponents = _ComponentClassToComponents;

-- Events related to component classes

--- Map of initialized Component Classes with values of Component Added Signals
-- @usage ComponentClassAddedEvents = {ComponentClass1 = Event1, ...}
ComponentClassAddedEvents = _ComponentClassAddedEvents;
--- Map of initialized Component Classes with values of Component Removed Signals
-- @usage ComponentClassRemovedEvents = {ComponentClass1 = Event1, ...}
ComponentClassRemovedEvents = _ComponentClassRemovedEvents;
--- Map of initialized Component Classes with values of Component Initialized Signals
-- @usage ComponentClassInitializedEvents = {ComponentClass1 = Event1, ...}
ComponentClassInitializedEvents = _ComponentClassInitializedEvents;
--- Signal for failed Component Class initialization
-- @usage ComponentClassInitializationFailed:Fire(ComponentClassName: string, Instance: Instance, Error: string)
ComponentClassInitializationFailed = XSignal.new();
};

Expand Down Expand Up @@ -227,6 +212,21 @@ function Rosyn.GetComponent<T>(Object: Instance, ComponentClass: T): T
return ComponentsForObject and ComponentsForObject[ComponentClass] or nil
end

local ExpectComponentParams = TypeGuard.Params(ValidGameObject, ValidComponentClass)
--- Asserts that a component exists on a given Instance.
function Rosyn.ExpectComponent<T>(Object: Instance, ComponentClass: T)
if (VALIDATE_PARAMS) then
ExpectComponentParams(Object, ComponentClass)
end

local Component = Rosyn.GetComponent(Object, ComponentClass)

if (Component == nil) then
error(ERR_COMPONENT_INSTANCE_NOT_FOUND:format(Rosyn.GetComponentName(ComponentClass), Object))
end

return Component
end

local AwaitComponentInitParams = TypeGuard.Params(ValidGameObject, ValidComponentClass, TypeGuard.Number():Optional())
--[[
Expand All @@ -253,7 +253,7 @@ function Rosyn.AwaitComponentInit<T>(Object: Instance, ComponentClass: T, Timeou
local OnInitialized = XSignal.new()
local ComponentName = Rosyn.GetComponentName(ComponentClass)

local InitializedConnection; InitializedConnection = Rosyn.GetInitializedEvent(ComponentClass):Connect(function(TargetInstance: Instance)
local InitializedConnection; InitializedConnection = Rosyn.GetComponentInstanceInitializedSignal(ComponentClass):Connect(function(TargetInstance: Instance)
if (TargetInstance ~= Object) then
return
end
Expand Down Expand Up @@ -401,7 +401,7 @@ function Rosyn._AddComponent(Object: Instance, ComponentClass)
---------------------------------------------------------------------------------------------------------
debug.profileend()

Rosyn.GetAddedEvent(ComponentClass):Fire(Object)
Rosyn.GetComponentInstanceAddedSignal(ComponentClass):Fire(Object)

-- Initialise component in separate coroutine
task.spawn(function()
Expand All @@ -427,7 +427,7 @@ function Rosyn._AddComponent(Object: Instance, ComponentClass)
end

NewComponent._INITIALIZED = true
Rosyn.GetInitializedEvent(ComponentClass):Fire(Object)
Rosyn.GetComponentInstanceInitializedSignal(ComponentClass):Fire(Object)
-- TODO: maybe we pcall and timeout the Initial and ensure Destroy is always called after
-- Otherwise we have to use the "retroactive" cleaner pattern
end)
Expand Down Expand Up @@ -486,7 +486,7 @@ function Rosyn._RemoveComponent(Object: Instance, ComponentClass)
---------------------------------------------------------------------------------------------------------
debug.profileend()

Rosyn.GetRemovedEvent(ComponentClass):Fire(Object)
Rosyn.GetComponentInstanceRemovedSignal(ComponentClass):Fire(Object)

-- Destroy component to let it clean stuff up
debug.profilebegin(DiagnosisTag .. DESTROY_SUFFIX)
Expand All @@ -501,14 +501,14 @@ function Rosyn._RemoveComponent(Object: Instance, ComponentClass)
debug.profileend()
end

local GetAddedEventParams = TypeGuard.Params(ValidComponentClass)
local GetComponentInstanceAddedSignalParams = TypeGuard.Params(ValidComponentClass)
--[[
Obtains or creates a Signal which will fire when a component has been instantiated.
@todo Refactor these 3 since they have a lot of repeated code
]]
function Rosyn.GetAddedEvent(ComponentClass): XSignal<Instance>
function Rosyn.GetComponentInstanceAddedSignal(ComponentClass): XSignal<Instance>
if (VALIDATE_PARAMS) then
GetAddedEventParams(ComponentClass)
GetComponentInstanceAddedSignalParams(ComponentClass)
end

local ComponentClassAddedEvents = Rosyn.ComponentClassAddedEvents
Expand All @@ -522,13 +522,13 @@ function Rosyn.GetAddedEvent(ComponentClass): XSignal<Instance>
return AddedEvent
end

local GetRemovedEventParams = TypeGuard.Params(ValidComponentClass)
local GetComponentInstanceRemovedSignalParams = TypeGuard.Params(ValidComponentClass)
--[[--
Obtains or creates a Signal which will fire when a component has been destroyed.
]]
function Rosyn.GetRemovedEvent(ComponentClass): XSignal<Instance>
function Rosyn.GetComponentInstanceRemovedSignal(ComponentClass): XSignal<Instance>
if (VALIDATE_PARAMS) then
GetRemovedEventParams(ComponentClass)
GetComponentInstanceRemovedSignalParams(ComponentClass)
end

local ComponentClassRemovedEvents = Rosyn.ComponentClassRemovedEvents
Expand All @@ -542,13 +542,13 @@ function Rosyn.GetRemovedEvent(ComponentClass): XSignal<Instance>
return RemovedEvent
end

local GetInitializedEventParams = TypeGuard.Params(ValidComponentClass)
local GetComponentInstanceInitializedSignalParams = TypeGuard.Params(ValidComponentClass)
--[[--
Obtains or creates a Signal which will fire when a component has passed its initialization phase.
]]
function Rosyn.GetInitializedEvent(ComponentClass): XSignal<Instance>
function Rosyn.GetComponentInstanceInitializedSignal(ComponentClass): XSignal<Instance>
if (VALIDATE_PARAMS) then
GetInitializedEventParams(ComponentClass)
GetComponentInstanceInitializedSignalParams(ComponentClass)
end

local ComponentClassInitializedEvents = Rosyn.ComponentClassInitializedEvents
Expand Down
2 changes: 1 addition & 1 deletion wally.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ license = "MIT"
authors = ["tpc9000"]

[dependencies]
TypeGuard = "tpc9000/[email protected].0"
TypeGuard = "tpc9000/[email protected].1"
Cleaner = "tpc9000/[email protected]"
XSignal = "tpc9000/[email protected]"

0 comments on commit c3a97d7

Please sign in to comment.