{"id":"tpc9000/xsignal","name":"xsignal","scope":"tpc9000","platform":"roblox","description":"Mirrored from the Wally registry.","version":"3.0.2","latest":"3.0.2","versions":["0.1.0","1.0.0","2.0.0","3.0.0","3.0.1","3.0.2"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{"tpc9000/typeguard":{"version":"^6.0.0-alpha.6","alias":"TypeGuard"}},"integrity":"e3343ebb18679a3e9938f21e7c42cc037ae54e86b1919606cd316ce35b62504b","likes":0,"downloads":0,"install":"forest install tpc9000/xsignal","url":"https://forest.dev/p/roblox/tpc9000/xsignal","files":"https://api.forest.dev/ai/package/roblox/tpc9000/xsignal/files","readme":"# XSignal\r\n\r\nE(X)tended signal is an API implementing Roblox's default Signal pattern, with some useful additions to help prevent memory leaks and manage multiple Signals. This should be compatible with any other Signal library. Only single args are supported for type system limitations, good practice, and performance reasons.\r\n\r\n## Usage Examples (additional features vs regular Signals)\r\n\r\n### 1: Wait timeouts with and without error throwing\r\n\r\n```lua\r\nlocal Test = XSignal.new() :: XSignal<number>\r\nlocal Result = Test:Wait(2) --> nil\r\nlocal SomethingElse = Test:Wait(2, true) -- Throws error due to timeout.\r\n```\r\n\r\n### 2: Signal extension\r\n\r\n```lua\r\n-- fromExtension wraps a Signal or XSignal, or a list of generic Signals or XSignals, and funnels invocations directly to the new constructed XSignal.\r\nXSignal.fromExtension({Workspace.ChildAdded, Players.PlayerAdded}):Connect(function(Item)\r\n    if (Item:IsA(\"BasePart\")) then\r\n        print(\"New part\")\r\n        return\r\n    end\r\n\r\n    print(\"New player\")\r\nend)\r\n\r\n-- Warning: packs variadics from other signal types into a table.\r\nXSignal.fromExtension({MarketplaceService.PromptProductPurchaseFinished}):Connect(function(Args)\r\n    local UserId = Args[1]\r\n    local ProductId = Args[2]\r\n    local IsPurchased = Args[3]\r\nend)\r\n```\r\n\r\n### 3: Data Validation\r\n\r\n```lua\r\nlocal Test = XSignal.new(function(Value)\r\n    assert(\r\n        (typeof(Value) == \"number\" and Value > 0 and Value < 10) or\r\n        (typeof(Value) == \"string\") or\r\n        (Value == nil),\r\n        \"Type mismatch\"\r\n    )\r\nend)\r\n\r\nTest:Fire(1) -- Accept\r\nTest:Fire(2) -- Accept\r\nTest:Fire(11) -- Reject\r\nTest:Fire() -- Reject\r\nTest:Fire(\"\") -- Reject\r\n\r\n-- Same as above but with TypeGuard: https://github.com/Novaly-Studios/TypeGuard\r\nlocal Another = XSignal.new(TypeGuard.Params(TypeGuard.Number(0, 10):Or(TypeGuard.String()):Optional())) :: XSignal<(number | string)?>\r\n```\r\n\r\n### 4: Waiting for the first of a list of Signals to fire\r\n\r\n```lua\r\nlocal Test1 = XSignal.new()\r\nlocal Test2 = XSignal.new()\r\nlocal Test3 = Players.PlayerAdded\r\n\r\ntask.delay(0.1, function()\r\n    Test2:Fire(\"Test2 fired\")\r\n    Test1:Fire(\"Test1 fired\")\r\nend)\r\n\r\nprint(XSignal.AwaitFirst({Test1, Test2, Test3}))\r\n--> Test2 fired\r\n\r\nXSignal.AwaitFirst({Test1, Test2, Test3}, 10, true) -- Optional timeout & error on timeout args\r\n```\r\n\r\n### 5: Waiting for all Signals in a list to fire\r\n\r\n```lua\r\nlocal Test1 = XSignal.new()\r\nlocal Test2 = XSignal.new()\r\nlocal Test3 = XSignal.new()\r\n\r\nlocal function FireThem()\r\n    Test1:Fire(\"Test1 fired\")\r\n    Test3:Fire(\"Test3 fired\")\r\n    Test2:Fire(\"Test2 fired\")\r\nend\r\n\r\ntask.delay(0.1, FireThem)\r\nprint(XSignal.AwaitAll({Test1, Test2, Test3}))\r\n--> {\"Test1 fired\", \"Test2 fired\", \"Test3 fired\"}\r\n-- Maintains order of the input signals.\r\n\r\n-- Optional timeout & error on timeout args.\r\nXSignal.AwaitAll({Test1, Test2, Test3}, 10, true)\r\n```\r\n\r\n### 6: Collecting values\r\n\r\n```lua\r\n-- Collect first 2 values.\r\nlocal Test = XSignal.new()\r\n\r\ntask.delay(0.1, function()\r\n    Test:Fire(\"Test1 fired\")\r\n    Test:Fire(\"Test2 fired\")\r\n    Test:Fire(\"Test3 fired\")\r\nend)\r\n\r\nlocal Result = Test:CollectN(2) -- Timeout & error on timeout args also supported.\r\nprint(Result) --> {\"Test1 fired\", \"Test2 fired\"}\r\n\r\n-- Yield & collect first value which meets a condition.\r\nlocal Test = XSignal.new()\r\nlocal Result\r\n\r\ntask.spawn(function()\r\n    Result = Test:CollectFirst(function(Value)\r\n        return Value > 10\r\n    end) -- Timeout & error on timeout args also supported.\r\nend)\r\n\r\nfor Count = 1, 15 do\r\n    Test:Fire(Count)\r\nend\r\n\r\nprint(Result) --> 11\r\n```\r\n\r\n### 7: Fast / threadless connection & firing\r\n\r\n```lua\r\n-- Sometimes if we know a function won't yield, we can use a threadless connection.\r\n-- Activates in the same coroutine. Be cautious.\r\nlocal Test = XSignal.new()\r\nTest:Connect(function(Value)\r\n    print(Value)\r\nend, XSignal.FastDirect)\r\nTest:Fire(1)\r\n\r\n-- Protected call version.\r\nlocal Test = XSignal.new()\r\nTest:Connect(function(Value)\r\n    if (math.random() > 0.5) then\r\n        error(\"Fail\")\r\n    end\r\n\r\n    print(Value)\r\nend, XSignal.Direct)\r\nTest:Fire(1)\r\n```\r\n\r\n### 8: Mapping values between signals\r\n\r\n```lua\r\nlocal Test = XSignal.new()\r\nlocal Stage1 = Test:Map(function(Value)\r\n    return Value * 2\r\nend)\r\nlocal Stage2 = Stage1:Map(function(Value)\r\n    return Value + 1\r\nend)\r\nStage2:Connect(function(Value)\r\n    print(\"Final\", Value)\r\nend)\r\nTest:Fire(4) --> Final 9\r\n```\r\n","readmeTruncated":false}