Skip to main content

Matter

Matter. It's what everything is made out of.

Types

ConnectionObject

type ConnectionObject = {
Disconnect(() → ())?,
Destroy(()->())?,
disconnect(() → ())?,
destroy(() → ())?
} | () → ()

A connection object returned by a custom event must be either a table with any of the following methods, or a cleanup function.

CustomEvent

interface CustomEvent {
Connect((...) → ConnectionObject)?
on((...) → ConnectionObject)?
connect((...) → ConnectionObject)?
}

A custom event must have any of these 3 methods.

Properties

World

Matter.World: World

Loop

Matter.Loop: Loop

Debugger

Matter.Debugger: Debugger

None

Matter.None: None

A value should be interpreted as nil when merging dictionaries.

Matter.None is used by Component:patch.

Functions

useHookState

Matter.useHookState(
discriminator?any,--

A unique value to additionally key by

cleanupCallback(storage{}) → boolean?--

A function to run when the storage for this hook is cleaned up

) → {}
tip

Don't use this function directly in your systems.

This function is used for implementing your own topologically-aware functions. It should not be used in your systems directly. You should use this function to implement your own utilities, similar to useEvent and useThrottle.

useHookState does one thing: it returns a table. An empty, pristine table. Here's the cool thing though: it always returns the same table, based on the script and line where your function (the function calling useHookState) was called.

Uniqueness

If your function is called multiple times from the same line, perhaps within a loop, the default behavior of useHookState is to uniquely identify these by call count, and will return a unique table for each call.

However, you can override this behavior: you can choose to key by any other value. This means that in addition to script and line number, the storage will also only return the same table if the unique value (otherwise known as the "discriminator") is the same.

Cleaning up

As a second optional parameter, you can pass a function that is automatically invoked when your storage is about to be cleaned up. This happens when your function (and by extension, useHookState) ceases to be called again next frame (keyed by script, line number, and discriminator).

Your cleanup callback is passed the storage table that's about to be cleaned up. You can then perform cleanup work, like disconnecting events.

Or, you could return true, and abort cleaning up altogether. If you abort cleanup, your storage will stick around another frame (even if your function wasn't called again). This can be used when you know that the user will (or might) eventually call your function again, even if they didn't this frame. (For example, caching a value for a number of seconds).

If cleanup is aborted, your cleanup function will continue to be called every frame, until you don't abort cleanup, or the user actually calls your function again.

Example: useThrottle

This is the entire implementation of the built-in useThrottle function:

local function cleanup(storage)
	return os.clock() < storage.expiry
end

local function useThrottle(seconds, discriminator)
	local storage = useHookState(discriminator, cleanup)

	if storage.time == nil or os.clock() - storage.time >= seconds then
		storage.time = os.clock()
		storage.expiry = os.clock() + seconds
		return true
	end

	return false
end

A lot of talk for something so simple, right?

component

Matter.component(
name?string,--

Optional name for debugging purposes

defaultData?{}--

Default data that will be merged with data passed to the component when created

) → Component--

Your new type of component

Creates a new type of component. Call the component as a function to create an instance of that component.

If defaultData is specified, it will be merged with data passed to the component when the component instance is created. Note that this is not fallback data: if you later remove a field from a component instance that is specified in the default data, it won't fall back to the value specified in default data.

-- Component:
local MyComponent = Matter.component("My component")

-- component instance:
local myComponentInstance = MyComponent({
	some = "data"
})

log

Matter.log(...any) → ()
Topologically-aware function

This function is only usable if called within the context of Loop:begin.

Logs some text. Readable in the Matter debugger.

useDeltaTime

Matter.useDeltaTime() → number
Topologically-aware function

This function is only usable if called within the context of Loop:begin.

Returns the os.clock() time delta between the start of this and last frame.

useEvent

Matter.useEvent(
instanceInstance | {[string]CustomEvent} | CustomEvent,--

The instance or the custom event, or a table that has the event you want to connect to

eventstring | RBXScriptSignal | CustomEvent--

The name of, or the actual event that you want to connect to

) → () → (
number,
...any
)
Topologically-aware function

This function is only usable if called within the context of Loop:begin.

Collects events that fire during the frame and allows iteration over event arguments.

for _, player in ipairs(Players:GetPlayers()) do
	for i, character in useEvent(player, "CharacterAdded") do
		world:spawn(
			Components.Target(),
			Components.Model({
				model = character,
			})
		)
	end
end

Returns an iterator function that returns an ever-increasing number, starting at 1, followed by any event arguments from the specified event.

Events are returned in the order that they were fired.

caution

useEvent keys storage uniquely identified by the script and line number useEvent was called from, and the first parameter (instance). If the second parameter, event, is not equal to the event passed in for this unique storage last frame, the old event is disconnected from and the new one is connected in its place.

Tl;dr: on a given line, you should hard-code a single event to connect to. Do not dynamically change the event with a variable. Dynamically changing the first parameter (instance) is fine.

for _, instance in pairs(someTable) do
	for i, arg1, arg2 in useEvent(instance, "Touched") do -- This is ok
	end
end

for _, instance in pairs(someTable) do
	local event = getEventSomehow()
	for i, arg1, arg2 in useEvent(instance, event) do -- PANIC! This is NOT OK
	end
end

If useEvent ceases to be called on the same line with the same instance and event, the event connection is disconnected from automatically.

You can also pass the actual event object instead of its name as the second parameter:

useEvent(instance, instance.Touched)
useEvent(instance, instance:GetPropertyChangedSignal("Name"))

useEvent supports custom events as well, so you can pass in an object with a Connect, connect or an on method. The object returned by any event must either be a cleanup function, or a table with a Disconnect or a Destroy method so that useEvent can later clean the event up when needed. See ConnectionObject for more information.

useThrottle

Matter.useThrottle(
secondsnumber,--

The number of seconds to throttle for

discriminator?any--

A unique value to additionally key by

) → boolean--

returns true every x seconds, otherwise false

Topologically-aware function

This function is only usable if called within the context of Loop:begin.

Utility for easy time-based throttling.

Accepts a duration, and returns true if it has been that long since the last time this function returned true. Always returns true the first time.

This function returns unique results keyed by script and line number. Additionally, uniqueness can be keyed by a unique value, which is passed as a second parameter. This is useful when iterating over a query result, as you can throttle doing something to each entity individually.

if useThrottle(1) then -- Keyed by script and line number only
	print("only prints every second")
end

for id, enemy in world:query(Enemy) do
	if useThrottle(5, id) then -- Keyed by script, line number, and the entity id
		print("Recalculate target...")
	end
end
Show raw api
{
    "functions": [
        {
            "name": "useHookState",
            "desc": ":::tip\n**Don't use this function directly in your systems.**\n\nThis function is used for implementing your own topologically-aware functions. It should not be used in your\nsystems directly. You should use this function to implement your own utilities, similar to `useEvent` and\n`useThrottle`.\n:::\n\n`useHookState` does one thing: it returns a table. An empty, pristine table. Here's the cool thing though:\nit always returns the *same* table, based on the script and line where *your function* (the function calling\n`useHookState`) was called.\n\n### Uniqueness\n\nIf your function is called multiple times from the same line, perhaps within a loop, the default behavior of\n`useHookState` is to uniquely identify these by call count, and will return a unique table for each call.\n\nHowever, you can override this behavior: you can choose to key by any other value. This means that in addition to\nscript and line number, the storage will also only return the same table if the unique value (otherwise known as the\n\"discriminator\") is the same.\n\n### Cleaning up\nAs a second optional parameter, you can pass a function that is automatically invoked when your storage is about\nto be cleaned up. This happens when your function (and by extension, `useHookState`) ceases to be called again\nnext frame (keyed by script, line number, and discriminator).\n\nYour cleanup callback is passed the storage table that's about to be cleaned up. You can then perform cleanup work,\nlike disconnecting events.\n\n*Or*, you could return `true`, and abort cleaning up altogether. If you abort cleanup, your storage will stick\naround another frame (even if your function wasn't called again). This can be used when you know that the user will\n(or might) eventually call your function again, even if they didn't this frame. (For example, caching a value for\na number of seconds).\n\nIf cleanup is aborted, your cleanup function will continue to be called every frame, until you don't abort cleanup,\nor the user actually calls your function again.\n\n### Example: useThrottle\n\nThis is the entire implementation of the built-in `useThrottle` function:\n\n```lua\nlocal function cleanup(storage)\n\treturn os.clock() < storage.expiry\nend\n\nlocal function useThrottle(seconds, discriminator)\n\tlocal storage = useHookState(discriminator, cleanup)\n\n\tif storage.time == nil or os.clock() - storage.time >= seconds then\n\t\tstorage.time = os.clock()\n\t\tstorage.expiry = os.clock() + seconds\n\t\treturn true\n\tend\n\n\treturn false\nend\n```\n\nA lot of talk for something so simple, right?",
            "params": [
                {
                    "name": "discriminator?",
                    "desc": "A unique value to additionally key by",
                    "lua_type": "any"
                },
                {
                    "name": "cleanupCallback",
                    "desc": "A function to run when the storage for this hook is cleaned up",
                    "lua_type": "(storage: {}) -> boolean?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "{}\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 122,
                "path": "lib/topoRuntime.luau"
            }
        },
        {
            "name": "component",
            "desc": "Creates a new type of component. Call the component as a function to create an instance of that component.\n\nIf `defaultData` is specified, it will be merged with data passed to the component when the component instance is\ncreated. Note that this is not *fallback* data: if you later remove a field from a component instance that is\nspecified in the default data, it won't fall back to the value specified in default data.\n\n```lua\n-- Component:\nlocal MyComponent = Matter.component(\"My component\")\n\n-- component instance:\nlocal myComponentInstance = MyComponent({\n\tsome = \"data\"\n})\n```",
            "params": [
                {
                    "name": "name?",
                    "desc": "Optional name for debugging purposes",
                    "lua_type": "string"
                },
                {
                    "name": "defaultData?",
                    "desc": "Default data that will be merged with data passed to the component when created",
                    "lua_type": "{}"
                }
            ],
            "returns": [
                {
                    "desc": "Your new type of component",
                    "lua_type": "Component"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 54,
                "path": "lib/init.luau"
            }
        },
        {
            "name": "log",
            "desc": ":::info Topologically-aware function\nThis function is only usable if called within the context of [`Loop:begin`](/api/Loop#begin).\n:::\n\n\nLogs some text. Readable in the Matter debugger.",
            "params": [
                {
                    "name": "...",
                    "desc": "",
                    "lua_type": "any"
                }
            ],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 15,
                "path": "lib/hooks/log.luau"
            }
        },
        {
            "name": "useDeltaTime",
            "desc": ":::info Topologically-aware function\nThis function is only usable if called within the context of [`Loop:begin`](/api/Loop#begin).\n:::\n\nReturns the `os.clock()` time delta between the start of this and last frame.",
            "params": [],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "number\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 12,
                "path": "lib/hooks/useDeltaTime.luau"
            }
        },
        {
            "name": "useEvent",
            "desc": ":::info Topologically-aware function\nThis function is only usable if called within the context of [`Loop:begin`](/api/Loop#begin).\n:::\n\nCollects events that fire during the frame and allows iteration over event arguments.\n\n```lua\nfor _, player in ipairs(Players:GetPlayers()) do\n\tfor i, character in useEvent(player, \"CharacterAdded\") do\n\t\tworld:spawn(\n\t\t\tComponents.Target(),\n\t\t\tComponents.Model({\n\t\t\t\tmodel = character,\n\t\t\t})\n\t\t)\n\tend\nend\n```\n\nReturns an iterator function that returns an ever-increasing number, starting at 1, followed by any event arguments\nfrom the specified event.\n\nEvents are returned in the order that they were fired.\n\n:::caution\n`useEvent` keys storage uniquely identified by **the script and line number** `useEvent` was called from, and the\nfirst parameter (instance). If the second parameter, `event`, is not equal to the event passed in for this unique\nstorage last frame, the old event is disconnected from and the new one is connected in its place.\n\nTl;dr: on a given line, you should hard-code a single event to connect to. Do not dynamically change the event with\na variable. Dynamically changing the first parameter (instance) is fine.\n\n```lua\nfor _, instance in pairs(someTable) do\n\tfor i, arg1, arg2 in useEvent(instance, \"Touched\") do -- This is ok\n\tend\nend\n\nfor _, instance in pairs(someTable) do\n\tlocal event = getEventSomehow()\n\tfor i, arg1, arg2 in useEvent(instance, event) do -- PANIC! This is NOT OK\n\tend\nend\n```\n:::\n\nIf `useEvent` ceases to be called on the same line with the same instance and event, the event connection is\ndisconnected from automatically.\n\nYou can also pass the actual event object instead of its name as the second parameter:\n\n```lua\nuseEvent(instance, instance.Touched)\nuseEvent(instance, instance:GetPropertyChangedSignal(\"Name\"))\n```\n\n`useEvent` supports custom events as well, so you can pass in an object with a `Connect`, `connect` or an `on` method.\nThe object returned by any event must either be a cleanup function, or a table with a `Disconnect` or a `Destroy`\nmethod so that `useEvent` can later clean the event up when needed.\tSee [ConnectionObject] for more information.",
            "params": [
                {
                    "name": "instance",
                    "desc": "The instance or the custom event, or a table that has the event you want to connect to",
                    "lua_type": "Instance | { [string]: CustomEvent } | CustomEvent"
                },
                {
                    "name": "event",
                    "desc": "The name of, or the actual event that you want to connect to",
                    "lua_type": "string | RBXScriptSignal | CustomEvent"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "() -> (number, ...any)\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 160,
                "path": "lib/hooks/useEvent.luau"
            }
        },
        {
            "name": "useThrottle",
            "desc": ":::info Topologically-aware function\nThis function is only usable if called within the context of [`Loop:begin`](/api/Loop#begin).\n:::\n\nUtility for easy time-based throttling.\n\nAccepts a duration, and returns `true` if it has been that long since the last time this function returned `true`.\nAlways returns `true` the first time.\n\nThis function returns unique results keyed by script and line number. Additionally, uniqueness can be keyed by a\nunique value, which is passed as a second parameter. This is useful when iterating over a query result, as you can\nthrottle doing something to each entity individually.\n\n```lua\nif useThrottle(1) then -- Keyed by script and line number only\n\tprint(\"only prints every second\")\nend\n\nfor id, enemy in world:query(Enemy) do\n\tif useThrottle(5, id) then -- Keyed by script, line number, and the entity id\n\t\tprint(\"Recalculate target...\")\n\tend\nend\n```",
            "params": [
                {
                    "name": "seconds",
                    "desc": "The number of seconds to throttle for",
                    "lua_type": "number"
                },
                {
                    "name": "discriminator?",
                    "desc": "A unique value to additionally key by",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "returns true every x seconds, otherwise false",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 39,
                "path": "lib/hooks/useThrottle.luau"
            }
        }
    ],
    "properties": [
        {
            "name": "World",
            "desc": "",
            "lua_type": "World",
            "source": {
                "line": 11,
                "path": "lib/init.luau"
            }
        },
        {
            "name": "Loop",
            "desc": "",
            "lua_type": "Loop",
            "source": {
                "line": 16,
                "path": "lib/init.luau"
            }
        },
        {
            "name": "Debugger",
            "desc": "",
            "lua_type": "Debugger",
            "source": {
                "line": 21,
                "path": "lib/init.luau"
            }
        },
        {
            "name": "None",
            "desc": "A value should be interpreted as nil when merging dictionaries.\n\n`Matter.None` is used by [`Component:patch`](/api/Component#patch).",
            "lua_type": "None",
            "source": {
                "line": 30,
                "path": "lib/init.luau"
            }
        }
    ],
    "types": [
        {
            "name": "ConnectionObject",
            "desc": "A connection object returned by a custom event must be either a table with any of the following methods, or a cleanup function.",
            "lua_type": "{Disconnect: (() -> ())?, Destroy: (() - >())?, disconnect: (() -> ())?, destroy: (() -> ())?} | () -> ()",
            "source": {
                "line": 84,
                "path": "lib/hooks/useEvent.luau"
            }
        },
        {
            "name": "CustomEvent",
            "desc": "A custom event must have any of these 3 methods.",
            "fields": [
                {
                    "name": "Connect",
                    "lua_type": "((...) -> ConnectionObject)?",
                    "desc": ""
                },
                {
                    "name": "on",
                    "lua_type": "((...) -> ConnectionObject)?",
                    "desc": ""
                },
                {
                    "name": "connect",
                    "lua_type": "((...) -> ConnectionObject)?",
                    "desc": ""
                }
            ],
            "source": {
                "line": 94,
                "path": "lib/hooks/useEvent.luau"
            }
        }
    ],
    "name": "Matter",
    "desc": "Matter. It's what everything is made out of.",
    "source": {
        "line": 6,
        "path": "lib/init.luau"
    }
}