# Classy
Classy library. Class is a tag-driven lifecycle manager that makes a composition approach much easier in luau!

## Why Use Classy?
- You have the option of tracking instances with classes or functions.
- Classy handles the cleanup of a class object's metatable and the provided [Janitor](https://github.com/howmanysmall/Janitor) object.
- Full generic type safety is preserved when accessing applied objects.
- If you are a fanatic about object-oriented programming, this is the module for you! Simply create classes for game objects and track them with tags.

## Installation
Classy is installable via [wally](https://wally.run/), and you can visit the page [here](https://wally.run/package/jaeymo/classy?version=1.0.1).

## Dependencies
- [Janitor](https://github.com/howmanysmall/Janitor)
- [Signal](https://github.com/Sleitnick/RbxUtil/blob/main/modules/signal/init.luau)

## Example Usage
```lua
--!strict

local Classy = require(path.to.classy)

-- Example class
local KillPartClass = {}
KillPartClass.__index = KillPartClass

export type KillPart = typeof(setmetatable({} :: {
    Part: BasePart,
    Janitor: Classy.Janitor,
}, KillPartClass))

-- every time a part with the "KillPart" tag is added, this runs.
function KillPartClass.new(Part: Instance, JanitorObject: Classy.Janitor): KillPart
    return setmetatable({
        Part = Part :: BasePart,
        Janitor = JanitorObject,
    }, KillPartClass)
end

function KillPartClass.Init(self: KillPart) -- automatically runs!
    self:WatchTouchedEvent()
end

function KillPartClass.DoSomething(self: KillPart)
    print(self)
end

function KillPartClass.WatchTouchedEvent(self: KillPart)
    self.Janitor:Add(self.Part.Touched:Connect(function(Hit: BasePart)
        local Humanoid = Hit.Parent and Hit.Parent:FindFirstChildWhichIsA("Humanoid")
        if not Humanoid then return end

        Humanoid.Health = 0
    end))
end

function KillPartClass.Destroy(self: KillPart)
    print("This object has been destroyed!") -- the Janitor and metatable are cleaned externally, no need to do it here.
end

-- Usage
local KillPartClassy = Classy.new("KillPart", KillPartClass, {
    ClassNames = { "BasePart" },
    Ancestors = { workspace },
})

KillPartClassy.InstanceAdded:Connect(function(Instance: Instance, Applied)
    Applied:GetData():DoSomething()
end)


# Luau Parser

A fast lexer and parser for Luau, written in pure Luau.

## API

### Lexer

**`Lexer.tokenize(source: string): ({Token}, {string})`**

Returns an array of tokens (and any error messages)

### Parser

**`Parser.parse(tokens: {Token}): (ASTNode?, {string})`**

Returns the AST root node (and any error messages)

**`Parser.format_node(node: ASTNode, indent: number): string`**

Formats an AST node for debugging

## Example Output

Input:
```lua
local x = 1 + 2 * 3

AST:
[1:1->1:19] Program
  [1:1->1:19] LocalDecl
    [1:7->1:7] Identifier: "x"
    [1:11->1:19] BinaryExpr: "+"
      [1:11->1:11] NumberLiteral: "1"
      [1:15->1:19] BinaryExpr: "*"
        [1:15->1:15] NumberLiteral: "2"
        [1:19->1:19] NumberLiteral: "3"

## Contributing

Contributions welcome!

## License

MIT License

## See Also

- [Luau](https://luau-lang.org/) - Official Luau language
- `example.luau` - Complex test case demonstrating parser capabilities

Made with ❤ | Last updated 13/12/2025


[store-page]: https://create.roblox.com/store/asset/5070921519
[itch-page]: https://cxmeel.itch.io/resurface
[github-page]: https://github.com/cxmeel/resurface-plugin
[youtube-video]: https://youtu.be/cl4tLTEiG48

[cover-image]: ./assets/banner.png
[button-roblox]: https://gist.githubusercontent.com/cxmeel/0dbc95191f239b631c3874f4ccf114e2/raw/roblox_dev-animated.svg
[button-itch]: https://gist.githubusercontent.com/cxmeel/0dbc95191f239b631c3874f4ccf114e2/raw/itch.svg
[list-grid-view]: ./assets/list_grid_views.png
[classic-mode]: ./assets/classic_mode.png

![Resurface][cover-image]

[![Get on Roblox][button-roblox]][store-page] [![Get on Itch][button-itch]][itch-page]

Give your experiences a classic Roblox vibe by restoring the ability to apply and convert surfaces to studs and more on your parts, models, meshes and unions.

## Installation

Resurface is an open source plugin, available via the Roblox Creator Store, Itch.io and [source code via GitHub][github-page].

### Creator Store

The Creator Store is the easiest way to install Resurface, with access to automatic updates and the ability to use Resurface anywhere you have Roblox Studio installed.

[![Get on Roblox][button-roblox]][store-page]

### Itch

If you would like to leave an optional donation to show your support, you can also purchase Resurface via Itch\.io.

[![Get on Itch][button-itch]][itch-page]

There's currently no way to automatically install and update plugins obtained via third-party storefronts. You'll need to manually download and drop the plugin file into your Studio plugins directory for each update. You will also need to repeat this process for every device you wish to use the plugin on.

### Open Source

If you'd like to inspect or clone Resurface's source code, it's available over on its official GitHub page.

- [View Source on GitHub →][github-page]

## Demo

[![View demo on YouTube](https://img.youtube.com/vi/cl4tLTEiG48/0.jpg)][youtube-video]
Video demo on YouTube

## Features

- Restore the legacy surface tool
- Apply surfaces to mesh and unions via MaterialVariants
- Accessibility toggles to adjust animations, reduce motion and transparency throughout the plugin UI
- Grid and list views available in surface picker
  ![Screenshot of Grid and List views][list-grid-view]
- "Classic Mode" for those that prefer the workflow of the legacy Surface Tool
  ![Screenshot of Classic Mode][classic-mode]

**More Plugins**\
[Codify](https://devforum.roblox.com/t/473076) | [Matswap](https://devforum.roblox.com/t/1078213) | [Resurface](https://devforum.roblox.com/t/1226536)

**Additonal Resources**\
[Markdown buttons](https://gist.github.com/cxmeel/b3af232eba0ace022e2fba8b7b286520)


* [View all by cxmeelcxmeel](https://cxmeel.itch.io)
  * [Follow cxmeelFollowFollowing cxmeelFollowing](https://itch.io/login?return_to=https%3A%2F%2Fcxmeel.itch.io%2Fresurface&intent=follow_user)
  * [Add To CollectionCollection](https://itch.io/login?return_to=https%3A%2F%2Fcxmeel.itch.io%2Fresurface&intent=add_to_collection)
  * [ Related gamesRelated](https://itch.io/games-like/2854924/resurface)

# Resurface

A downloadable plugin

[Download Now](https://cxmeel.itch.io/resurface/purchase)Name your own price

Resurface is available via the Roblox Creator Store. If you'd like to leave an optional donation, you can do so through Itch.io.

You will not receive anything extra for purchasing the plugin, and will be redirected to the Creator Store after your purchase.

If you would like to inspect the source of Resurface, it's available over on the official GitHub project page:

<https://github.com/cxmeel/resurface-plugin>

[More information](javascript:void\(0\))

Status| [In development](https://itch.io/misc/in-development)
Category| [Other](https://itch.io/misc)
Rating| Rated 4.0 out of 5 stars(1 total ratings)
Author| [cxmeel](https://cxmeel.itch.io)
Tags| [roblox](https://itch.io/misc/tag-roblox), [roblox-plugin](https://itch.io/misc/tag-roblox-plugin), [roblox-studio](https://itch.io/misc/tag-roblox-studio), [roblox-studio-plugin](https://itch.io/misc/tag-roblox-studio-plugin)
Links| [Creator Store](https://create.roblox.com/store/asset/5070921519/Resurface-Convert-surfaces-to-studs-and-more), [DevForum](https://devforum.roblox.com/t/surface-tool-resurface-bring-back-studs/1226536)

## Download

[Download Now](https://cxmeel.itch.io/resurface/purchase)Name your own price

Click download now to get access to the following files:

**from Roblox Creator Store**

External

[](https://img.itch.zone/aW1hZ2UvMjg1NDkyNC8xNzgwOTc0My5wbmc=/original/YRBG2Q.png)[](https://img.itch.zone/aW1hZ2UvMjg1NDkyNC8xNzgwOTc0NC5wbmc=/original/V7GT9M.png)

[](https://itch.io/)[itch.io](https://itch.io/)Â·[View all by cxmeel](https://cxmeel.itch.io)Â·[Report](javascript:void\(0\);)Â·[Embed](javascript:void\(0\);)


# About
Val is a simple, lightweight state management library for Roblox & Luau. Inspired by reactive programming, Val allows you to observe values so that you can automate processes for value changes.

## Why Val?
- Zero dependencies (source code is just a single file!)
- Easy/simple API that minimizes verbosity
- Focused on general purpose state reactivity rather than UI exclusively

## Features
You can go over these features in-depth in the [documentation](https://tumblewede.github.io/Val/docs/introduction).
- Computeds
- Observers
- Scopes
- Batched updates
- Memory management
- Built-in arithmetic & string operations
- (Near) full customization of states


Skip to main content

### Zero Dependencies

The source code for the Val library is a single file.

### Universal Reactivity

Val contains only one class to represent states, computeds, and scopes. This means that all of Val's features can be applied to all Val objects.

### Simple API

Val is a beginner-friendly approach to state management designed to be as simple as possible while remaining a powerful library.


Let's begin by creating some simple logic:

    local num = 20
    num = 40
    print(num) -- 40

In the example above, all we did was create a number, modify it, and print its value.

Now let's do the same thing using Val:

    local num = Val.new(20)
    num:set(40)
    print(num:get()) -- 40

This time, `num` is a state object and not a primitive. To set the value, we simply use the `set` method. To get the value, we just use `get`.

Now, let's try a more legitimate example:

    local label = script.Parent.TextLabel -- assume this exists
    local num = 20
    label.Text = "Number: " .. num -- Number: 20
    num = 40
    label.Text = "Number: " .. num -- Number: 40
    num = 70
    label.Text = "Number: " .. num -- Number: 70

One issue with this logic is that we have to manually update `label.Text` every time we change `num` because it is a primitive that cannot be observed.

Now let's try the same thing with Val:

    local label = script.Parent.TextLabel -- assume this exists
    local num = Val.new(20)
    num:on(function(value)
        label.Text = "Number: " .. num
    end, true) -- Number: 20
    num:set(40) -- Number: 40
    num:set(70) -- Number: 70

Now, all we have to do is change the value of `num`, and `label.Text` will automatically update through the `on` method.

Although code this small may not demand the use of a state management library like Val, synchronizing values with displays in large codebases can quickly become messy. Luckily, state management libraries like Val make this synchronization much more manageable.

In the next pages, I will talk more about how to use the features of Val.


# Wally

Add the following dependency into your dependencies list in `wally.toml`:

    [dependencies]
    Val = "tumblewede/val@0.1.1"

Then, run `wally install` in your project's terminal to install the dependency.

Restoring Type Safety

Unfortunately, you cannot access types from Wally packages due to the structure of Packages. To fix this, we can install [wally-package-types](https://github.com/JohnnyMorganz/wally-package-types) as a workaround.

To install [wally-package-types](https://github.com/JohnnyMorganz/wally-package-types), run the following command in your terminal:

    cargo install wally-package-types

If your project does not have a sourcemap, then run the following command in your project's terminal:

    rojo sourcemap default.project.json --output sourcemap.json

Finally, run the command:

    wally-package-types --sourcemap sourcemap.json Packages/

# Roblox Studio

  1. Go to the [Releases page](https://github.com/TumbleWede/Val/releases) of the GitHub repository
  2. Open up the Assets dropdown of the version you'd like to use (ideally the latest version)
  3. Download the `Val.rbxm` file
  4. Drag & drop the `Val.rbxm` into your Roblox Studio game
  5. Relocate the module if needed (ideally in a libraries/modules folder in ReplicatedStorage)

# Source Code

  1. Go to the [Releases page](https://github.com/TumbleWede/Val/releases) of the GitHub repository
  2. Open up the Assets dropdown of the version you'd like to use (ideally the latest version)
  3. Download the `Val.luau` file
  4.      * If you are using Rojo, then drag & drop the `Val.luau` into somewhere in your project.
     * If you are using Roblox Studio, then copy and paste the contents of `Val.luau` into a new `ModuleScript` named `Val`.


Val has many operator methods that help reduce the code needed to perform operations on Val object.

Using these operator methods also reduces the number of calls per operation, thus being more efficient than performing them manually (as seen in the Val equivalents).

These methods will not check if the operation is valid, so trying to do something devious like dividing a string will result in an error.

# Assignment Operators

Assume `a` is a `Val<T>` and `b` is a `T`, where `T` is any type. If `b` were to be a `Val<T>`, then you would need to call `b:get()` inside each assignment call (e.g. `a:add(b:get())`).

Name| Operator| Val Method| Val Equivalent
---|---|---|---
Addition| `a += b`| `a:add(b)`| `a:set(a:get() + b)`
Subtraction| `a -= b`| `a:sub(b)`| `a:set(a:get() - b)`
Multiplication| `a *= b`| `a:mul(b)`| `a:set(a:get() * b)`
Division| `a /= b`| `a:div(b)`| `a:set(a:get() / b)`
Floor Division| `a //= b`| `a:idiv(b)`| `a:set(a:get() // b)`
Modulus| `a %= b`| `a:mod(b)`| `a:set(a:get() % b)`
Exponentiation| `a ^= b`| `a:pow(b)`| `a:set(a:get() ^ b)`
Concatenation| `a ..= b`| `a:cat(b)`| `a:set(a:get() .. b)`
Toggling| `a = not a`| `a:flip()`| `a:set(not a:get())`

Additionally, you can also chain these methods together:

    local x = Val.new(2)
    x:pow(3):div(4):add(5):mul(6) -- (((x ^ 3) / 4) + 5) * 6
    print(x:get()) -- 42

# Math Operations

These methods behave the same as assignment operators.

Name| Operation| Val Method| Val Equivalent
---|---|---|---
Min Value| `a = math.min(a, b, ...)`| `a:min(b, ...)`| `a:set(math.min(a:get(), b, ...))`
Max Value| `a = math.max(a, b, ...)`| `a:max(b, ...)`| `a:set(math.max(a:get(), b, ...))`
Clamp| `a = math.clamp(a, min, max)`| `a:clamp(min, max)`| `a:set(math.clamp(a:get(), min, max))`
Absolute Value| `a = math.abs(a)`| `a:abs()`| `a:set(math.abs(a:get()))`
Floor| `a = math.floor(a)`| `a:floor()`| `a:set(math.floor(a:get()))`
Ceiling| `a = math.ceil(a)`| `a:ceil()`| `a:set(math.ceil(a:get()))`
Round| `a = math.round(a)`| `a:round()`| `a:set(math.round(a:get()))`
Snap| `a = math.round(a / b) * b`| `a:snap(b)`| `a:set(math.round(a:get() / b) * b)`
Lerp| `a = math.lerp(a, b, t)`| `a:lerp(b, t)`| `a:set(math.lerp(a:get(), b, t))`

# Relational Operators

Assume both `a` and `b` are a `Val<T>`, since these methods are meant to be used exclusively for comparing `Val` objects.

Name| Operator| Val Method| Val Equivalent
---|---|---|---
Equal to| `a == b`| `a:eq(b)`| `a:get() == b:get()`
Less than| `a < b`| `a:lt(b)`| `a:get() < b:get()`
Less than or equal to| `a <= b`| `a:le(b)`| `a:get() <= b:get()`
Greater than| `a > b`| `a:gt(b)`| `a:get() > b:get()`
Greater than or equal to| `a >= b`| `a:ge(b)`| `a:get() >= b:get()`


# Usage

Use `Val:copy()` to clone a state:

    local a = Val.new(20)
    local b = a:copy()
    print(a == b) -- false (implying b is a separate state)
    print(a:eq(b)) -- true
    a:set(10)
    print(b:get()) -- 20

However, `Val:copy()` only creates a shallow clone of the state. This means that any object, including internal states, will share the same reference:

    local t1 = Val.scope {
        Val.new(1),
        Val.new(2),
        Val.new(3)
    local t2 = t1:copy()

    t2[1]:die()
    print(t1[1]:isdead()) -- true

note

Val currently does not support deep cloning, but this is subject to change in the future if it becomes a significant limitation.


# Usage

Observers are event listeners that are called whenever the state changes its value:

    local x = Val.new(2)
    local disconnect = x:on(function(newValue, oldValue, wasCalledImmediately)
        print(newValue, oldValue, wasCalledImmediately)
    end, true) -- 2 2 true
    x:set(4) -- 4 2 false
    x:set(8) -- 8 4 false
    x:set(8) -- no output because value did not change
    x:set(8, true) -- 8 8 false
    disconnect()
    x:set(16) -- no output

We created an observer by calling the `Val:on` method, which takes in a callback function and an optional boolean.

The callback function takes in three optional values:

  * `newValue` \- the state's newly updated value
  * `oldValue` \- the state's previous value
  * `wasCalledImmediately` \- if the callback was called immediately

In most cases, you will only need `newValue`.

The second optional parameter, if set to `true`, indicates that we want to call the observer immediately. If `false` or `nil`, the observer will not be called until the next state update.

The `Val:on` method returns a disconnect callback. When we no longer need to listen to a value, we can call this disconnect. In the example above, our observer would display any changes of the value of `x`, but this observer is destroyed after we called `disconnect()`

In the example above, we used `Val:set` with a second optional boolean parameter that, when `true`, would cause the method to call its observers even if the value did not actually change.


# Usage

This library supports computed states, which are essentially states whose values are derived from dependent states. When a dependent state changes, the computed state will automatically update along with it. This means that you cannot change the value of a computed state manually.

Let's look at a basic example:

    local a = Val.new(3)
    local b = Val.new(4)
    local h = Val.calc(function(get)
        return math.sqrt(get(a)^2 + get(b)^2)
    end)

    print(h:get()) -- 5
    a:set(6)
    b:set(8)
    print(h:get()) -- 10

In this example, we created a computed that calculates the hypotenuse, `h`, of a right triangle based on the side lengths, `a` and `b`.

We used the `Val.calc` constructor to create the computed, which we passed in a callback function as the argument. This function will return the value of the computed based on the values of the other states we want to use (`a` and `b` in this case). When `a` or `b` updates its value, the callback will be called, thus allowing it to update its value.

Notice how the callback takes in a function called `get` that we use instead of the `Val:get()` method. This special `get` function tells the computed which values it depends on, so if we just used `a:get()` instead of `get(a)`, then `h` would not update if the value of `a` were to change.

note

You can call `get(Val<T>)` on the same state multiple times safely; the resulting computed will refer to the value only once, even if you multi-call `get(Val<T>)`.

Let's look at another triangle example below:

    local a, b, c = Val.new(3), Val.new(4), Val.new(5)
    local s = Val.calc(function(get)
        return (get(a) + get(b) + get(c)) / 2
    end)
    -- A = sqrt(s * (s-a) * (s-b) * (s-c)), where s = (a+b+c) / 2
    local area = Val.calc(function(get)
        local s2 = get(s)
        return math.sqrt(s2 * (s2 - get(a)) * (s2 - get(b)) * (s2 - get(c)))
    end)

    print(area:get()) -- 6
    a:set(6)
    b:set(8)
    c:set(10)
    print(area:get()) -- 24

This example represents Heron's Formula, which calculates the area of a triangle given only its three side lengths. However, this formula needs a variable, `s`, which is equal to half of the sum of the three side lengths. Luckily, we can accomplish this using a computed, so we never have to worry about updating `s` when we need to update one of the side lengths.

Computed states are still considered states, which means that we can also use them in other computeds. In this example, we used the computed `s` inside the computed `area`. This means that when a side length changes, `s` will update which will also update `area`.

note

Although chaining computeds like this is possible, there is one drawback, where the final computed may fire multiple times rather than once. In this case, if we set the value of a side length, `s` will update, but since `area` depends on both the side lengths AND `s`, this means that `area` will update twice instead of once. For small calculations like in this example, it's not really a problem, but if you were to have expensive computeds, it would be better to avoid this practice.

# Observer Behavior with Computeds

Observers for computeds behave the exact same as observers for states:

    local s = Val.new(10)
    local area = Val.calc(function(get)
        return get(s) ^ 2
    end)

    s:on(function(new)
        print("Side length set to", new)
    end)
    area:on(function(new)
        print("Area updated to", new)
    end)

    s:set(20)
    -- Side length set to 20
    -- Area updated to 400

note

Due to typechecking limitations, `Val.calc` will return `Val<any>`, so the computed will not have an inferred generic type. To fix this, you can simply typecast the computed to a `Val.Type<T>`


Val supports batching, where we can set multiple states in a batch that will only call observers after all the values have been set.

Let's look at a basic example of setting a state without batching:

    local x = Val.new(2)
    x:on(function(value)
        print("x set to", value)
    end)
    x:set(3) -- x set to 3
    x:set(4) -- x set to 4
    x:set(5) -- x set to 5

In this case, the observer will be called after each set. Now let's do the same thing but with batching:

    local x = Val.new(2)
    x:on(function(value)
        print("x set to", value)
    end)

    Val.batch(function(set)
        set(x, 3) -- no output
        set(x, 4) -- no output
        set(x, 5) -- no output
    end) -- x set to 5

This time, setting the value of x will not call the observer until after the batch is done. This means that the observer will only be called once instead of three times.

The `Val.batch` function took in a callback function as the argument, which took in a set function. The `set(Val<T>, T)` function allows us to set the value of the state without immediately calling the observer. If `Val:set` were to be used inside the batch callback, then the observer would immediately be called.

note

  * You cannot use any of the assignment operator methods inside the batch, as just like `Val:set`, they would immediately call the observer inside the batch. You would have to set the value manually, so `a:add(b)` would look like `set(a, a:get() + b)`
  * Computeds cannot be batched because of their reliance on observers. As mentioned previously about redundant updates when chaining computeds, there is not a way to solve it with batching in Val.


# Usage

To destroy a state, simply use `Val:die()`. To check if a state has been destroyed, call either `Val:isdead()` or `Val.isdead(Val<T>)` (all methods can be called as static such as `Val.set(Val<T>, T)`):

    local x = Val.new(20)
    x:set(30)
    print(x:isdead()) -- false
    x:die()
    x:set(40) -- error
    print(x:isdead()) -- true

After a value has been destroyed, you can no longer use its methods other than `Val:die()` and `Val:isdead()`. All of its contents will be removed, and trying to access any fields may result in an error.

If a computed is dependent on a state that has been destroyed, then the computed will also be destroyed:

    local side = Val.new(5)
    local area = Val.calc(function(get)
        return get(side) ^ 2
    end)

    print(side:isdead()) -- false
    print(area:isdead()) -- false
    side:die()
    print(side:isdead()) -- true
    print(area:isdead()) -- true

tip

`Val:Destroy()` is an alias for `Val:die()` for better compatibility with cleanup libraries like [Janitor](https://howmanysmall.github.io/Janitor/)

# Destroying Scopes

By default, all states inside the dying state will be destroyed along with the state. Alternatively, you can call `Val:die(true)`, where the `true` argument indicates that all internal states will be dereferenced rather than destroyed:

    local t1 = Val.new({
        Val.new(1),
        Val.new(2),
        Val.new(3)
    })
    t1:die() -- 1, 2, and 3 states will also be destroyed

    local t2 = Val.scope {
        Val.new(1),
        Val.new(2),
        Val.new(3),
        a = Val.new(1),
        b = Val.new(2),
        c = Val.new(3),
    t2:die() -- All six states will be destroyed

    local x = Val.new(1)
    local t3 = Val.scope {
        a = x,
        b = Val.new(2),
        c = Val.new(3),
    t3:die() -- a, b, and c will be destroyed.
    print(x:isdead()) -- true

    local y = Val.new(1)
    local t4 = Val.scope {
        a = y,
        b = Val.new(2),
        c = Val.new(3),
    t4:die(true) -- a, b, and c will be dereferenced but alive.
    -- y will remain alive, but the values of t4.b and t4.c will be garbage collected

note

`Val:isdead()` and `Val.isdead(Val<T>)` return the same result, but the two behave differently if the state is dead.

  * `Val.isdead(Val<T>)` will compare the metatable of the state to the metatable of a dead state
  * `Val:isdead()` is an exception for accessing methods of a dead state, in which the `__index` metamethod will refer to a function that returns true.

If the state is alive, `Val:isdead()` will refer to `Val.isdead(self)`.


# Intro

In Val, scopes are Val objects with no value with the intention of storing other values.

Before we dive into using `Val.scope`, let's first create a Val with fields:

    local rect = Val.new({
        size = Val.new(Vector2.new(5, 3)),
        pos = Val.new(Vector2.new(6, 12))
    })

In this example, we created a state with fields `size` and `pos`. However, we must call `Val:get` to access these fields (e.g. `rect:get().size`).

We can skip this step by adding the fields directly inside the state object rather than inside the state's value:

    local rect = Val.none() -- alias for Val.new(nil)
    rect.size = Val.new(Vector2.new(5, 3))
    rect.pos = Val.new(Vector2.new(6, 12))

This time, we can directly access `size` and `pos` (e.g. `rect.size`) without the need of `rect:get()`. Val does not stop you from modifying or accessing keys inside any states (unless the state is dead), so we can get away with this safely as long as we don't override any internal/private fields.

warning

Avoid adding custom fields with the following names: `_value`, `_listeners`, `_dependents`, `_disconnects`, `_eval`, and `set`

# Using `Val.scope`

Now let's do the same thing using `Val.scope`:

    local rect = Val.scope {
        size = Val.new(Vector2.new(5, 3)),
        pos = Val.new(Vector2.new(6, 12))

This code example will give us the same result as the previous example: a valueless state with custom fields. The benefit of using `Val.scope` instead of `Val.none()` is that you can store your custom fields in a table argument.

For dictionaries, using `Val.scope` or `Val.none()` is based on your preference, but you will absolutely need to use `Val.scope` for arrays:

    local nums = Val.scope {
        Val.new(1),
        Val.new(2),
        Val.new(3),
        Val.new(4)

In this example, the list of states will be directly inside `nums` rather than inside its value.

# Iteration

You can safely iterate through a scope's keys without iterating through the state's private fields:

    local t = Val.scope {
        a = Val.new(1),
        b = Val.new(2),
        c = Val.new(3)

    for i, v in t do
        print(i, v:get())
    end
    -- a 1
    -- b 2
    -- c 3

Because we used generic iteration, the for loop utilized the scope's iteration metamethod which will ensure that private keys will not be iterated through.

Now let's do the same thing for an array:

    local t = Val.scope {
        Val.new(7),
        Val.new(8),
        Val.new(9)

    for i, v in ipairs(t) do
        print(i, v:get())
    end
    -- 1 7
    -- 2 8
    -- 3 9

In this example, we used `ipairs` instead of generic iteration to iterate through the indices. Although generic iteration could have produced the exact same result, `ipairs` is faster.

tip

Always use `ipairs` over generic iteration if you only need to use the index-value pairs and not key-value pairs.

danger

Using `pairs` will iterate through the state's private keys, which can lead to undefined behavior. Always use either `ipairs` or generic iteration for scopes.

Now let's do an example of combining numeric and string keys in the scope. In this case, there is a significant difference in each iteration method:

    local t = Val.scope {
        a = Val.new(1),
        b = Val.new(2),
        c = Val.new(3),
        Val.new(7),
        Val.new(8),
        Val.new(9)

    -- generic iteration
    for i, v in t do
        print(i, v:get())
    end
    -- 1 7
    -- 2 8
    -- 3 9
    -- a 1
    -- b 2
    -- c 3

    -- ipairs
    for i, v in ipairs(t) do
        print(i, v:get())
    end
    -- 1 7
    -- 2 8
    -- 3 9

    -- pairs (why you should avoid this)
    for i, v in pairs(t) do
        print(i, v:get())
    end
    -- 1 7
    -- 2 8
    -- 3 9
    -- a 1
    -- error: attempt to call missing method 'get' of table

In this example, all three iterator methods produced a different result:

  * Generic iteration printed every custom key-value/index-value pair
  * `ipairs` only printed every index-value pair
  * `pairs` resulted in an error after the for loop tried to access a private key and assumed it was a state

# Observer Behavior with Scopes

note

Val currently does not have special observer behavior for scopes, but this is subject to change in the future if it becomes a significant limitation.

Observers do not work directly on scopes because they listen to the value of the scope, which is typically just `nil`. However, you can simply overcome this by adding observers to the fields and not the scope:

    local rect = Val.scope {
        pos = Val.new(Vector2.new(2, 6)),
        size = Val.new(Vector2.new(4, 3))

    local onChange = function()
        print("Rectangle of size", rect.size:get(), "located at", rect.pos:get())
    end
    rect.pos:on(onChange)
    rect.size:on(onChange, true) -- Rectangle of size 4, 3 located at 2, 6
    rect.pos:set(Vector2.new(5, 10)) -- Rectangle of size 4, 3 located at 5, 10
    rect.size:set(Vector2.new(1, 2)) -- Rectangle of size 1, 2 located at 5, 10

When working with an unknown list of index-value pairs, we can use a table to keep track of observers for each index:

    local list = Val.scope {
        Val.new(1),
        Val.new(2),
        Val.new(3)

    local onChange = function()
        local sum = 0
        for i, v in ipairs(list) do
            sum += v:get()
        end
        print("The sum of the values of the list is", sum)
    end

    local observers = {}
    for i, v in ipairs(list) do
        observers[i] = v:on(onChange)
    end
    list[1]:set(list[1]:get(), true) -- Force update the value to immediately call the observer once
    -- The sum of the values of the list is 6
    list[2]:set(10) -- The sum of the values of the list is 14

    -- Destroy scope
    for i, v in ipairs(observers) do
        v()
    end
    observers = nil -- Also dereferences the disconnects
    list:die() -- Also destroys the three values

tip

Remember that you only need to use Val when you care about observing/reacting to value changes. If you have a field that you don't need to actively listen to, it does not need to be a Val object. You also only really need to use a scope rather than a regular table if/when you plan on destroying all the internal states at once for memory management purposes.


# Luaup

Luaup is a lossless Luau parser written in Luau. It is intended to be used within Roblox Studio plugins, as Roblox Studio offers no way to parse Luau or interact with a Luau AST.

## Documentation

There is currently no available documentation. If you have questions, feel free to ask me in the Roblox OSS Community discord server, linked here: https://discord.gg/mchCdAFPWU.


# impulse
Luau physics engine that works off of sequential impulses.
It also aims to fully reimplement Roblox's humanoid physics

## Usage
This project uses [Roblox Studio](https://github.com/lune-org/lune) to visualize, although any environment that uses [Luau](https://luau.org/) will work.
This project can be built through [Rojo](https://rojo.space)

```bash
git clone "https://github.com/magicoal-nerb/luau-physics.git"
cd lua-luau
rojo serve

## Features
- Recreation of Roblox's humanoid physics
- One shot contact manifold generation
- Uses sequential impulses to solve contact constraints
- Optimized broadphase using dynamic BVHs that use tree rotations
- Warm starting, sleeping, stable box stacking
- Works with any convex shape, models can be specified with a wavefront or support function
- General shapecasting, raycasting, and blockcasting (can outperform Roblox's query performance suprisingly...)

## Goals
- Creating an efficient physics engine in Luau
- Making a fine-tunable physics engine that is heavily customizable for developers' needs
    - reliable touches
    - a steppable world without needing an external plugin or headless studio client
    - hopefully a valuable resource for hobbyists creating their own physics engines
- Future proofing Roblox's Humanoid physics, hopefully to the point where it can be used as a substitute humanoid for obbying or server authorative movement

## Planned features
- Dynamic contact islands
- Experimenting with block solving contact points under PGS/direct solves if sufficient
- Other constraints such as hinges, prismatic constraints, etc...
- Specialized narrowphase checks for primitives
- Weld graph

## Showcase
https://github.com/user-attachments/assets/2ed574bd-29b7-4d0d-9e25-926b3e5598dd

## Contributing
Any contributions are greatly appreciated! I might alter pull requests for consistent coding style and performance, but please try keeping code clean!!

Shield: [![CC BY 4.0][cc-by-shield]][cc-by]

This work is licensed under a
[Creative Commons Attribution 4.0 International License][cc-by].

[![CC BY 4.0][cc-by-image]][cc-by]

[cc-by]: http://creativecommons.org/licenses/by/4.0/
[cc-by-image]: https://i.creativecommons.org/l/by/4.0/88x31.png
[cc-by-shield]: https://img.shields.io/badge/License-CC%20BY%204.0-lightgrey.svg


# vLuau
vLuau is a Luau VM with compiler which can be ran in Roblox.
Like vLua but with Fully Luau Support. (native code generation and some optimizations are excepted)
You can use this module instead of loadstring in roblox but with sandboxed bytecode.
## Credits
- [vLuau](https://github.com/kosuke14/vLuau) - [me (kosuke14)](https://github.com/kosuke14)
- [Fiu](https://github.com/TheGreatSageEqualToHeaven/Fiu) (Luau Bytecode Interpreter) - [TheGreatSageEqualToHeaven](https://github.com/TheGreatSageEqualToHeaven) and [Rerumu](https://github.com/Rerumu)
- [LuauCeption](https://github.com/RadiatedExodus/LuauCeption) (Running Luau inside Luau) - [RadiatedExodus](https://github.com/RadiatedExodus), [phoriah](https://github.com/phoriah), and [jLn0n](https://github.com/jLn0n)
## Usage
```lua
local loadstring = require(path.to.MainModule) -- requiring vLuau MainModule

loadstring(<(source | bytecode):string>[, table:env, string:chunkname, luau_settings:table]): function
-- compile code and build function
loadstring.luau_execute(...)
-- same as above

loadstring.luau_compile(<source:string>[, chunkname:string]): string
-- compile code and return bytecode

loadstring.luau_load([, env:table, luau_settings:table]): function
-- build function with provided compiled bytecode

-- luau_settings can be generated with
loadstring.create_settings()

-- to create custom env
loadstring.create_env(envwriter:table) -- envwriter should be table of additional globals

-- Examples:
loadstring("print('hello world!')", getfenv(0))()
-- (OUTPUT) hello world!
loadstring("a", nil, 'syntaxTest')()
-- (ERROR) syntaxTest:1: Incomplete statement: expected assignment or a function call
Roblox Model is available at [here](https://create.roblox.com/store/asset/108059886578663) (trolled rblx fr)
For detailed informations about `luau_settings`: [Fiu](https://github.com/rce-incorporated/Fiu?tab=readme-ov-file#settings)
## Building
1. Go [LuauCeption](https://github.com/RadiatedExodus/LuauCeption) and build them as compiler with latest Luau
2. Replace Ception.luau with the built version.
3. Place Instances in Roblox like this:
├── <ModuleScript> MainModule (init.lua)
|   ├── <ModuleScript> Ception (Ception.luau)
└── └── <ModuleScript> Fiu (Fiu.luau)
> Building with Rojo is not recommended because Roblox doesn't allow to set very long script source via `Script.Source` method.
### Tips to add large 'ModuleScript' into Roblox Studio
Roblox Studio could be crashed when you try to paste the large script into Roblox Studio.
This tips may solve that problem.
1. Open some place on Studio.
2. Right click somewhere in Explorer, and click on 'Insert from file'.
3. Find the large script to insert.
4. Name the script to something to locate it easily.
5. Save place or the script as XML format (place: .rbxlx, model: .rbxmx)
6. Open the saved file with your text editor.
7. Find the script item.
8. Change `<Item class="Script"` to `<Item class="ModuleScript"` then save it.
9. Open the file as Studio and the script is now ModuleScript.


# Typeforge

A Type Function utility library for Luau.

docs: http://typeforge.luau.page/

wally: https://wally.run/package/cameronpcampbell/typeforge

> [!TIP]
> Check out [Typebrick](https://github.com/typeforge-luau/typebrick), a type function utility library for Roblox types.


Skip to content

# Typeforge

Type Function Utilities For Luau

[Docs](http://typeforge.luau.page/docs)


# Omit[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L1182-L1200)

Returns a subset of a type without specified components / properties.

## Params

Name| Type| Description
---|---|---
input| `type`| The type to omit from.
to_omit| `type`| The components / properties to omit.

## Example

luau

    type Result = Omit<
        "hello" | "world" | "foo" | "bar",
        "world" | "bar"

    -- type Result = "foo" | "hello"

# Pick[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L1218-L1236)

Returns a subset of a type with only specified components / properties.

## Params

Name| Type| Description
---|---|---
input| `type`| The type to pick from.
to_pick| `type`| The components / properties to pick.

## Example

luau

    type Result = Pick<
        "hello" & "world" & "foo" & "bar",
        "world" | "bar"

    -- type Result = "bar" & "world"

# Flatten[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L1259-L1263)

Combines a nested union or intersection into one union / intersection. Combines an intersection of tables into one consolidated table whilst preserving semantics.

## Params

Name| Type| Description
---|---|---
input| `type`| The type to flatten.

## Example

luau

    type Result = Flatten<({ hello: "world" } & ({ foo: "bar" } | { lol: "kek" }))>

        type Result = {
            foo: "bar",
            hello: "world"
            hello: "world",
            lol: "kek"

# Clean[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L1285-L1311)

Removes duplicate components / properties from a type.

## Params

Name| Type| Description
---|---|---
input| `type`| The type to clean.

## Example

luau

    type Result = Clean<{
        age: number | number,
        [boolean]: boolean | boolean

        type Result = {
            [boolean]: boolean,
            age: number

# ValueOf[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L1331-L1377)

Returns all of the values in a table type.

## Params

Name| Type| Description
---|---|---
input| `type`| The table type to get values from.

## Example

luau

    type Result = ValueOf<
            ["1"]: FooType,
            ["2"]: BarType,
            [number]: BazType,
        },

    -- type Result = FooType | BarType | BazType


# Inspect[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L2523-L2551)

Returns the inputted type but with unions, intersections and negations turned into tables so they can be inspected / debugged better.

## Params

Name| Type| Description
---|---|---
input| `type`| The type to be inspected.

## Example

luau

    type Result = Inspect<"hello" | ("world" & string)>

        type Result = {
            1: "hello",
            2: {
                1: "world",
                2: string,
                kind: "intersection"
            },
            kind: "union"

# Expect[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L2580-L2594)

Throws a type error if the first type does not equal the second.

## Params

Name| Type| Description
---|---|---
expect| `type`| The type to be compared.
toBe| `type`| The type you want to compare `expect` to.

## Example

luau

    type test = Expect<
        { foo: "bar" } & { hello: "world" },
        (string, boolean) -> ...type

        TypeError: 'Expect' type function errored at runtime: [string "Expect"]:2295:
        Expected the function type below:
        > (string, boolean) -> (...type)

        But Got this intersection type instead:
        >     foo: "bar"
        > } & {
        >     hello: "world"

# Negate[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L2612-L2614)

Negates a type.

TEMPORARY

This type function will be removed once luau gets negation syntax (see the `negation-types` RFC).

## Params

Name| Type| Description
---|---|---
input| `type`| The type to stringify.

## Example

luau

    type Result = Negate<string>

    -- type Result = ~string


# FlattenFunction[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L2310-L2352)

Flattens unions, intersections and tables inside function parameter and return types.

## Params

Name| Type| Description
---|---|---
input| `(...type) -> (...type)`| The function to flatten.

## Example

luau

    type Result = FlattenFunction<(string | (number | boolean)) -> (type & (boolean & number))>

    -- type Result = (boolean | number | string) -> type & boolean & number

# CleanFunction[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L2366-L2408)

Removes duplicate components / properties from function param and return types.

## Params

Name| Type| Description
---|---|---
input| `(...type) -> (...type)`| The function to clean.

## Example

luau

    type Result = CleanFunction<(number, string | string | boolean) -> type | type>

    -- type Result = (number, boolean | string) -> type

# Params[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L2429-L2456)

Gets the parameters for a function.

## Params

Name| Type| Description
---|---|---
input| `(...type) -> (...type)`| The function to get params for.

## Example

luau

    type Result = Params<(number, string, boolean, ...{ type }) -> type>

        type Result = {
            1: number,
            2: string,
            3: boolean,
            tail: {type}

# Returns[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L2470-L2497)

Gets the return types for a function.

## Params

Name| Type| Description
---|---|---
input| `(...type) -> (...type)`| The function to get return types for.

## Example

luau

    type Result = Returns<() -> (string, number)>

    -- type Result = { 1: string, 2: number }


# OmitUnion[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L1397-L1416)

Returns a subset of a union without specified components.

## Params

Name| Type| Description
---|---|---
input| `type`| The union to omit from.
to_omit| `type`| The components to omit.

## Example

luau

    type Result = OmitUnion<
        "hello" | string | "world",
        "world"

    -- type Result = "hello" | string

# PickUnion[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L1434-L1453)

Returns a subset of a union with only specified components.

## Params

Name| Type| Description
---|---|---
input| `type`| The union to pick from.
to_pick| `type`| The components to pick.

## Example

luau

    type Result = PickUnion<
        "hello" | string | "world",
        "world"

    -- type Result = "world"

# FlattenUnion[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L1467-L1479)

Recursively combines nested unions into one union.

## Params

Name| Type| Description
---|---|---
input| `type`| The union to flatten.

## Example

luau

    type Result = FlattenUnion<"foo" | ("hello" | ("world" | "lol"))>

    -- type Result = "foo" | "hello" | "lol" | "world"

# CleanUnion[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L1493-L1508)

Removes duplicate types from a union.

## Params

Name| Type| Description
---|---|---
input| `type`| The union to clean.

## Example

luau

    type Result = CleanUnion<"hello" | string | "world" | string | "foo" | "hello">

    -- type Result = "foo" | "hello" | "world" | string


# OmitTableByKeys[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L1664-L1688)

Returns a subset of a table without properties whose keys contain the specified components.

## Params

Name| Type| Description
---|---|---
input| `{ [type]: type }`| The table to omit from.
to_omit| `type`| The values of the properties to omit.

## Example

luau

    type Result = OmitTableByKeys<{ hello: string, foo: "bar", bar: number }, "hello" | "foo">

    -- type Result = type Result = { bar: number }

# OmitTableByValues[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L1703-L1727)

Returns a subset of a table without properties whose values contain the specified components.

## Params

Name| Type| Description
---|---|---
input| `{ [type]: type }`| The table to omit from.
to_omit| `type`| The values of the properties to omit.

## Example

luau

    type Result = OmitTableByValues<{ hello: string, foo: "bar", bar: number }, "bar" | string>

    -- type Result = type Result = { bar: number }

# PickTableByKeys[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L1742-L1766)

Returns a subset of a table with only properties whose keys contain the specified components.

## Params

Name| Type| Description
---|---|---
input| `{ [type]: type }`| The table to pick from.
to_pick| `type`| The keys of the properties to pick.

## Example

luau

    type Result = PickTableByKeys<{ hello: string, foo: "bar", bar: number }, "hello" | "foo">

    -- type Result = type Result = { foo: "bar", hello: string }

# PickTableByValues[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L1781-L1805)

Returns a subset of a table with only properties whose values contain the specified components.

## Params

Name| Type| Description
---|---|---
input| `{ [type]: type }`| The table to pick from.
to_pick| `type`| The values of the properties to pick.

## Example

luau

    type Result = PickTableByValues<{ hello: string, foo: "bar", bar: number }, "bar" | string>

    -- type Result = type Result = { foo: "bar", hello: string }

# FlattenTable[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L1828-L1953)

Combines an intersection of tables into one consolidated table whilst preserving semantics.

## Params

Name| Type| Description
---|---|---
input| `{ [type]: type }`| The table to flatten.

## Example

luau

    type Result = FlattenTable<
        { name: string, salary: number } &
        { kind: "employee" }

        type Result = {
            name: string,
            salary: number,
            kind: "employee"

# CleanTable[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L1968-L2022)

Removes duplicate types from union and intersection keys and values inside the specified table.

## Params

Name| Type| Description
---|---|---
input| `{ [type]: type }`| The table to clean.

## Example

luau

    type Result = TableClean<{ name: string | string, salary: number }>

    -- type Result = { name: string, salary: number }

# Partial[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L2068-L2070)

Makes every property in a table optional.

## Params

Name| Type| Description
---|---|---
input| `{ [type]: type }`| The table to make partial.

## Example

luau

    type Result = Partial<{ hello: "world", foo: "bar" }>

    -- type Result = { foo: "bar"?, hello: "world"? }

# DeepPartial[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L2084-L2086)

Makes every property (including nested ones) in a table optional.

## Params

Name| Type| Description
---|---|---
input| `{ [type]: type }`| The table to make deeply partial.

## Example

luau

    type Result = DeepPartial<{ hello: "world", foo: { bar: "baz" } }>

    -- type Result = { foo: { bar: "baz"? }?, hello: "world"? }

# Required[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L2121-L2123)

Makes every property in a table required.

## Params

Name| Type| Description
---|---|---
input| `{ [type]: type }`| The table to make required.

## Example

luau

    type Result = Required<{ hello: "world"?, foo: "bar"? }>

    -- type Result = { foo: "bar", hello: "world" }

# DeepRequired[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L2137-L2139)

Makes every property (including nested ones) in a table required.

## Params

Name| Type| Description
---|---|---
input| `{ [type]: type }`| The table to make deeply required.

## Example

luau

    type Result = DeepRequired<{ hello: { lol: "kek"? }?, foo: "bar"? }>

    -- type Result = { foo: { lol: "kek" }, hello: "world" }

# ReadOnly[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L2153-L2173)

Makes every property in a table read only.

## Params

Name| Type| Description
---|---|---
input| `{ [type]: type }`| The table to make read only.

## Example

luau

    type Result = ReadOnly<{ hello: "world", foo: "bar" }>

    -- type Result = { read foo: "bar", read hello: "world" }

# DeepReadOnly[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L2200-L2212)

Makes every property (including nested ones) in a table read only.

## Params

Name| Type| Description
---|---|---
input| `{ [type]: type }`| The table to make deeply read only.

## Example

luau

    type Result = DeepReadOnly<{ hello: "world", foo: { bar: "baz" } }>

    -- type Result = { read foo: { read bar: "baz" }, read hello: "world" }

# ReadWrite[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L2226-L2251)

Makes every property in a table readable and writable.

## Params

Name| Type| Description
---|---|---
input| `{ [type]: type }`| The table to make readable and writable.

## Example

luau

    type Result = ReadWrite<{ read hello: "world", foo: "bar" }>

    -- type Result = { foo: "bar", hello: "world" }

# DeepReadWrite[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L2282-L2294)

Makes every property (including nested ones) in a table readable and writable.

## Params

Name| Type| Description
---|---|---
input| `{ [type]: type }`| The table to make deeply readable and writable.

## Example

luau

    type Result = DeepReadWrite<{ read hello: "world", foo: { read bar: "baz" } }>

    -- type Result = { foo: { bar: "baz" }, hello: "world" }


# OmitIntersection[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L1528-L1547)

Returns a subset of an intersection without specified components.

## Params

Name| Type| Description
---|---|---
input| `type`| The intersection to omit from.
to_omit| `type`| The components to omit.

## Example

luau

    type Result = OmitIntersection<
        "hello" & string & "world",
        "world"

    -- type Result = "hello" & string

# PickIntersection[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L1565-L1586)

Returns a subset of an intersection with only specified components.

## Params

Name| Type| Description
---|---|---
input| `type`| The intersection to pick from.
to_pick| `type`| The components to pick.

## Example

luau

    type Result = PickIntersection<
        "hello" & string & "world",
        "world"

    -- type Result = "world"

# FlattenIntersection[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L1600-L1616)

Combines nested intersections into one intersection.

## Params

Name| Type| Description
---|---|---
input| `type`| The intersection to flatten.

## Example

luau

    type Result = FlattenIntersection<"foo" & ("hello" & ("world" & "lol"))>

    -- type Result = "foo" & "hello" & "lol" & "world"

# CleanIntersection[](https://github.com/typeforge-luau/typeforge/blob/main/src/init.luau#L1630-L1647)

Removes duplicate types from an intersection.

## Params

Name| Type| Description
---|---|---
input| `type`| The intersection to clean.

## Example

luau

    type Result = CleanIntersection<"hello" & string & "world" & string & "foo" & "hello">

    -- type Result = "foo" & "hello" & "world" & string


# lumin/framework

A feather light framework for Luau games.

## Prerequisites

In order to use **lumin/framework** you must have the following dependencies installed:

* [`pesde@^0.6.0`](https://github.com/pesde-pkg/pesde)
* [`rojo@^7.4.4`](https://github.com/rojo-rbx/rojo)

## Usage

This usage examples creates a new, and basic provider that uses a dependency.

```luau
local Dependency = require(path.to.dependency)

local function Init()
    Dependency.NuclearExplosion()
end

return {
    Init = Init,
    Uses = { Dependency } -- Makes the dependency init before this module

## License

This project is licensed under the [MIT](https://github.com/lumin-org/framework/blob/main/LICENSE) license


# 🍵 GreenTea
An experimental runtime typechecker for Roblox Luau, with matching 'tooling-time' Luau types.

[Docs](https://corecii.github.io/GreenTea/)

## Features
- Check types at runtime (like [the t package](https://github.com/osyrisrblx/t))
- Build Luau types and runtime types simultaneously (so you have less duplicate code!)
- Pretty errors that tell you exactly where you went wrong, even for unions and intersections
- Inspect types at runtime, so you can procedurally check types in weird scenarios, such as when checking a set of Instance attributes.
- Infer types from values
- `t` compatibility (via `GreenTea.t` — all `t` tests pass!)

## Experimental
While this package has had a lot of work put into it, it's largely an experiment. It's a first iteration, so the API can use some improvement — especially the structure of the generated Type objects, which could have a better shape.

Please leave feedback on the issues page. Thank you!

## Install

### with Wally (for Rojo)
1. [Install Wally](https://wally.run/install)
2. Add `GreenTea = "corecii/greentea@0.4.11"` to your `wally.toml`

### with pesde (for Rojo or Lune)
1. [Install pesde](https://docs.pesde.daimond113.com/installation)
2. Run `pesde add corecii/greentea`\
   or add `GreenTea = { name = "corecii/greentea", version = "^0.4.11" }` to your `pesde.toml`

### Standalone (for non-Rojo)
- Download from [the Releases page](https://github.com/corecii/greentea/releases)

## How Does It Work?

GreenTea's runtime code looks like a typical runtime typechecker:
you call functions to create typecheckers and compose them together.

It has two tricks:
1. Its function type definitions _lie_ and say that it's returning _the type that you're checking for_ instead of a GreenTea.Type object.
  So while you're composing a runtime type, you're _also_ composing a Luau type definition!
2. The typechecker it produces is actually an _object_, which gives you a runtime-inspectable
  type definition you can do more advanced stuff with. This also means prettier errors that look a lot like Luau type errors.

## Examples

### Easy Mode

```lua
local gt = require(path.to.GreenTea)

local stringType = gt.build(gt.string())

-- equivalent to takesString(value: string)
local function takesString(value: typeof(stringType:type()))
    -- will error if value is not a string
    stringType:assert(value)

    -- ...
end

Using `gt` is recommended, because `GreenTea` is a lot to type and `gt` is not!

### How it Works

```lua
local stringTypeRaw = gt.string()

-- stringTypeRaw is typed in Luau as `string`
-- but at runtime, it's really a GreenTea.Type object.
-- This makes it easy to split it up into a runtime
-- typechecker and a Luau type:

-- give a name to the type
type stringTypeLuau = typeof(stringTypeRaw:type())

-- cast the object to the runtime typechecker type it really is
local stringTypeChecker = gt.typecast(stringTypeRaw)

-- You can think of this like "tricking" Luau into
-- "giving" us a `string` type.
-- This is all `GreenTea.build` does -- it typecasts
-- the value into a GreenTea.Type object, but includes
-- the original type os you can use `typeof`.

-- But the magic is this also works for more complex types!

local catTypeRaw = gt.table({
    age = gt.number(),
    meowSound = gt.string(),
    breed = {
        [gt.string()] = gt.number(),
    },
})

type catTypeLuau = typeof(catTypeRaw)
-- which is the same as...
type catTypeSame = {
    age: number,
    meowSound: string,
    breed: { [string]: number },

-- and we can get a runtime typechecker...
local catTypeChecker = gt.typecast(catTypeRaw)

-- OR we can make it really easy on ourselves if we just use `build` from the beginning:

local catType = gt.build(gt.table{
    age = gt.number(),
    meowSound = gt.string(),
    breed = {
        [gt.indexer(gt.string())] = gt.number(),
    },
}))

-- catType == catTypeChecker
-- catTypeLuau == typeof(catTypeChecker:type())

Using `build` is recommended because it makes this mostly type-safe:
- The returned value is properly typed a GreenTea Type object, so you
  have typechecked access to GreenTea.Type's properties and methods.
- TypeObject.type() can still be passed to GreenTea constructors to
  pass the GreenTea type in.
- `build` runs `GreenTea.typeof` internally, so you can pass it simple tables
  and they'll be converted to the proper `GreenTea.table` type.

In this way, "built" types are for direct use, and "unbuilt" types are for composition
with other GreenTea constructors.

### Taking in GreenTea types

GreenTea is great for libraries that want to do typechecking, like RemoteEvent wrappers, because you can make the API very ergonomic:

```lua
-- RemoteWrapper.luau

function RemoteWrapper.new<T>(greenTeaType: T): RemoteWrapper<T>
    local self = {
        typechecker = GreenTea.build(greenTeaType),
        ...

    return setmetatable(self, RemoteWrapper)
end

function RemoteWrapper.onServerEvent<T>(self: RemoteWrapper<T>, fn: (player: Player, params: T) -> ())
    self.event:OnServerEvent(function(player: Player, paramsMaybe: any?)
        local params = self.typechecker:assert(params)
        fn(player, params)
    end)
end

```lua
-- CatSpawner.luau

local SpawnCat = RemoteWrapper.new({
    cat = {
        age = gt.number(),
        meowSound = gt.string(),
        ...
    },
    location = gt.CFrame(),
    ...
})

SpawnVehicle:onServerEvent(function(player, params)
    -- params is properly typed as { cat: { age: number, ...}, location: CFrame, ... }
end)

## Lune Support

GreenTea has not been extensively tested on Lune.
Some Roblox-specific typecheckers might not work, and there may be other issues.
Please report any issues here: https://github.com/corecii/greentea/issues

## `t` Compatibility

`GreenTea.t` includes all of the methods from the `t` package. See [the t package's readme](https://github.com/osyrisrblx/t/blob/master/README.md) for more details.

### As a drop-in replacement

You can include the `greentea-t-standalone` package as an easy, drop-in replacement for a codebase which already uses `t`. Just add it to your `wally.toml`:

> **with Wally (for Rojo)**
> 1. [Install Wally](https://wally.run/install)
> 2. Add `t = "corecii/greentea-t-standalone@0.4.11"` to your `wally.toml`

> **with pesde (for Rojo)**
> 1. [Install pesde](https://docs.pesde.daimond113.com/installation)
> 2. Add `t = { name = "corecii/greentea_t_standalone", version = "^0.4.11" }` to your `pesde.toml`

This package just exports `GreenTea.t` to make drop-in replacement easy.

## Inspiration

This library was largely inspired by `t`, but also my experience working with t types, working with Luau types, and working with systems that need inspectable type definitions. This is my first attempt at solving all of these problems. I'd really like to see the inversion of this library -- where inspectable runtime type definitions are built from Luau types. But that's more work, so I'll leave it as a TODO!

## Future Plans
- Split up `GreenTea.luau` into multiple files. It's too long. But I'm tired and want to move on and actually use this library for cool things.
- Stop lying to the typechecker once it's able to handle all of GreenTea's features.
- Inverted GreenTea, where runtime typecheckers are built from Luau types.


# About:
Penumbra is performant modular graphics rendering pipeline which leans into Luau's strengths.

This provides developers who wish to render shaders in constrained environments (ie. no GPU access).

The project contains many example fragment shaders to get started, and is configured by default to run `fragment/multi.luau`.

Each major module has its purpose described at the top, and comments are provided to help for context.

# Setup - Luau:
The Penumbra directory needs to be sat as a direct child of the Pajamas directory in order to run.

Ensure you get it set up successfully in your host runtime before continuing.

Link: https://github.com/Crazyblox/Pajamas

# Setup - Roblox:
Look for the latest release of Penumbra [here](https://github.com/Crazyblox/penumbra/releases/) and download the .rbxm for the drag and drop version of Penumbra which is packaged with the Pajamas runtime.

# Contributions:
Issues & PRs are welcome; the intent of Penumbra is to remain as minimal and elegant as possible.

What will and what won't be considered is a list I intend to make more clear as the project progresses, but this is a rough start:
- Before making a PR, create an issue for discussion to see if your intent aligns with this projects goals.
- PRs with performance implications will be scrutinised heavily. Ensure optimisation, pros/cons, potential drawbacks and repro testing methods are provided.
- Shader modules which stand out as their own example among the others in the list will be considered; if porting someone else's work, ensure MIT licensing and source is linked. Adhere to the format in which shader_builder outputs.


A relatively fast, pure Luau PNG library

Lightweight, performant, and memory-efficient Luau implementation of the PNG image format!

### What this library does:

🟢  Extracting all critical PNG chunks: `IHDR`, `PLTE` and `IDAT`

🟢  Decoding RGBA pixel data from PNGs of any bit depth and color type

🟢  Decoding simple transparency via the `tRNS` chunk

🟢  Modest error checking for invalid or corrupt PNG data

### What this library does...n't:

🔴  Encoding PNG data (planned)

🔴  Parsing chunks besides the ones listed above (planned)

🔴  Decoding interlaced PNGs (planned)

🔴  Decoding PNGs larger than ~1GB, this is due to [**Luau buffer limitations**](https://luau.org/library#buffer-library)

## Installation

### Using Roblox
`.rbxm` binaries can be downloaded and imported directly from the [**Releases**](https://github.com/LiterallyWize/PNGuin/releases/latest) page.

### Using [pesde](https://github.com/pesde-pkg/pesde)
[**PNGuin is available as a pesde package**](https://pesde.dev/packages/literallywize/pnguin), and can be installed using the following commands:
```bash
pesde add literallywize/pnguin
pesde install

### Documentation can be found on the [wiki](https://github.com/LiterallyWize/PNGuin/wiki)


# literallywize/pnguin

v1.0.1 Â· published ...

A relatively fast, pure Luau PNG library

Target

Luau

A relatively fast, pure Luau PNG library

[ ](https://github.com/LiterallyWize/PNGuin/releases/latest) [ ](https://github.com/LiterallyWize/PNGuin/blob/main/LICENSE)

Lightweight, performant, and memory-efficient Luau implementation of the PNG image format!

### What this library does:

ð¢ âExtracting all critical PNG chunks: `IHDR`, `PLTE` and `IDAT`

ð¢ âDecoding RGBA pixel data from PNGs of any bit depth and color type

ð¢ âDecoding simple transparency via the `tRNS` chunk

ð¢ âModest error checking for invalid or corrupt PNG data

### What this library does...n't:

ð´ âEncoding PNG data (planned)

ð´ âParsing chunks besides the ones listed above (planned)

ð´ âDecoding interlaced PNGs (planned)

ð´ âDecoding PNGs larger than ~1GB, this is due to [**Luau buffer limitations**](https://luau.org/library#buffer-library)

## Installation

### Using Roblox

`.rbxm` binaries can be downloaded and imported directly from the [**Releases**](https://github.com/LiterallyWize/PNGuin/releases/latest) page.

### Using [pesde](https://github.com/pesde-pkg/pesde)

[PNGuin is available as a pesde package](https://pesde.dev/packages/literallywize/pnguin), and can be installed using the following commands:

    pesde add literallywize/pnguin
    pesde install

### Usage Example:

    local PNGuin = require(path.to.PNGuin)

    local png -- Your PNG binary data

    local pngInfo = PNGuin.Extract(png)
    local pixelData, elapsed = pngInfo:DecodeRGBA()


[ LiterallyWize ](https://github.com/LiterallyWize) / ** [pnguin](https://github.com/LiterallyWize/pnguin) ** Public

  * [ Notifications ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin) You must be signed in to change notification settings
  * [ Fork 0 ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin)
  * [ Star  7 ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin)

# Home

Jump to bottom

LiterallyWize edited this page Nov 8, 2025 · [13 revisions](https://github.com/LiterallyWize/pnguin/wiki/Home/_history)

# Welcome to the [](https://github.com/LiterallyWize/PNGuin)Wiki!

## [Installation](https://github.com/LiterallyWize/PNGuin/wiki/Installation)

  * [Using Roblox](https://github.com/LiterallyWize/PNGuin/wiki/Installation#using-roblox)
  * [Using pesde](https://github.com/LiterallyWize/PNGuin/wiki/Installation#using-pesde)

## [Decoding](https://github.com/LiterallyWize/PNGuin/wiki/Decoding)

  * [Basic Usage](https://github.com/LiterallyWize/PNGuin/wiki/Decoding#basic-usage)
  * [Functions](https://github.com/LiterallyWize/PNGuin/wiki/Decoding#functions)
  * [Types](https://github.com/LiterallyWize/PNGuin/wiki/Decoding#types)

## [Encoding (planned)](https://github.com/LiterallyWize/PNGuin/wiki/Encoding)

## [Home](https://github.com/LiterallyWize/PNGuin/wiki/Home)

## [Installation](https://github.com/LiterallyWize/PNGuin/wiki/Installation)

  * [Using Roblox](https://github.com/LiterallyWize/PNGuin/wiki/Installation#using-roblox)
  * [Using pesde](https://github.com/LiterallyWize/PNGuin/wiki/Installation#using-pesde)

## [Decoding](https://github.com/LiterallyWize/PNGuin/wiki/Decoding)

  * [Basic Usage](https://github.com/LiterallyWize/PNGuin/wiki/Decoding#basic-usage)
  * [Functions](https://github.com/LiterallyWize/PNGuin/wiki/Decoding#functions)
  * [Types](https://github.com/LiterallyWize/PNGuin/wiki/Decoding#types)

## [Encoding (planned)](https://github.com/LiterallyWize/PNGuin/wiki/Encoding)

### Clone this wiki locally


# literallywize/pnguin

v1.0.1 Â· published ...

A relatively fast, pure Luau PNG library

Target

Luau

## [1.0.1 (latest)](https://pesde.dev/packages/literallywize/pnguin/1.0.1/any)

... Â·  Luau

## [1.0.0 ](https://pesde.dev/packages/literallywize/pnguin/1.0.0/any)

... Â·  Luau


# literallywize/pnguin

v1.0.1 Â· published ...

A relatively fast, pure Luau PNG library

Target

Luau

This package doesn't have any dependencies.


[ LiterallyWize ](https://github.com/LiterallyWize) / ** [pnguin](https://github.com/LiterallyWize/pnguin) ** Public

  * [ Notifications ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin) You must be signed in to change notification settings
  * [ Fork 0 ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin)
  * [ Star  7 ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin)

# Decoding

Jump to bottom

LiterallyWize edited this page Nov 8, 2025 · [6 revisions](https://github.com/LiterallyWize/pnguin/wiki/Decoding/_history)

### Basic Usage:

    local PNGuin = require(path.to.PNGuin)

    local png -- Your PNG data

    local pngInfo = PNGuin.Extract(png)
    print(`width: {pngInfo.width}, height: {pngInfo.height}`)

    local pixelData, elapsed = pngInfo:DecodeRGBA()
    print(`decoded in {elapsed} seconds`)

    -- pixelData is now a buffer of RGBA ordered pixel data
    -- e.g. pixelData can be written to an EditableImage

## Functions

### `PNGuin.Extract(input)`

Parses and extracts info from binary PNG data

Parameter | Type | Description
---|---|---
input |  `buffer` / `string` | Input PNG data
Return | Description
**PNGInfo** | Extracted info

### `PNGInfo:DecodeRGBA()`

Decodes pixel data into a `buffer` in RGBA order

Return | Description
`buffer` | RGBA pixel data
`number` | Time taken to decode in seconds

## Types

The below types are subject to change _heavily_ in major versions!

### `PNGInfo`

Property | Type | Values | Description
---|---|---|---
IDAT | `buffer` | N/A | Contains all collected [**IDAT**](https://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.IDAT) chunk data
PLTE | `buffer` | N/A | Contains [**PLTE**](https://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.PLTE) chunk data
tRNS | `buffer?` | N/A | Contains [**tRNS**](https://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.tRNS) chunk data
width | `number` | 0-232 | Width of image (pixels)
height | `number` | 0-232 | Height of image (pixels)
bitDepth | `number` | 1-2-4-8-16 | Bit depth of image from [**IHDR**](https://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.IHDR)
colorType | `ColorType` | **ColorType** | Color type of image from [**IHDR**](https://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.IHDR)
channels | `number` | 1-4 | Number of channels based on color type
DecodeRGBA | `function` | N/A | See **PNGInfo:DecodeRGBA()**
**See code**

        IDAT: buffer,
        PLTE: buffer,
        tRNS: buffer?,

        width: number,
        height: number,

        bitDepth: number,
        colorType: ColorType,
        hasAlpha: boolean,
        channels: number,

        DecodeRGBA: (self: PNGInfo) -> (buffer, number)

### `ColorType`

Type | Values
`string` | Truecolor / Grayscale / Indexed
**See code**

    "Truecolor" | "Grayscale" | "Indexed"

[« Previous (Installation)](https://github.com/LiterallyWize/PNGuin/wiki/Installation)  [Next (Encoding) »](https://github.com/LiterallyWize/PNGuin/wiki/Encoding)

## [Home](https://github.com/LiterallyWize/PNGuin/wiki/Home)

## [Installation](https://github.com/LiterallyWize/PNGuin/wiki/Installation)

  * [Using Roblox](https://github.com/LiterallyWize/PNGuin/wiki/Installation#using-roblox)
  * [Using pesde](https://github.com/LiterallyWize/PNGuin/wiki/Installation#using-pesde)

## [Decoding](https://github.com/LiterallyWize/PNGuin/wiki/Decoding)

  * [Basic Usage](https://github.com/LiterallyWize/PNGuin/wiki/Decoding#basic-usage)
  * [Functions](https://github.com/LiterallyWize/PNGuin/wiki/Decoding#functions)
  * [Types](https://github.com/LiterallyWize/PNGuin/wiki/Decoding#types)

## [Encoding (planned)](https://github.com/LiterallyWize/PNGuin/wiki/Encoding)

### Clone this wiki locally


[ LiterallyWize ](https://github.com/LiterallyWize) / ** [pnguin](https://github.com/LiterallyWize/pnguin) ** Public

  * [ Notifications ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin) You must be signed in to change notification settings
  * [ Fork 0 ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin)
  * [ Star  7 ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin)

# Encoding

Jump to bottom

LiterallyWize edited this page Nov 8, 2025 · [3 revisions](https://github.com/LiterallyWize/pnguin/wiki/Encoding/_history)

### Encoding is currently not supported, planned to be implemented in v3.0.0

[« Previous (Decoding)](https://github.com/LiterallyWize/PNGuin/wiki/Decoding)

## [Home](https://github.com/LiterallyWize/PNGuin/wiki/Home)

## [Installation](https://github.com/LiterallyWize/PNGuin/wiki/Installation)

  * [Using Roblox](https://github.com/LiterallyWize/PNGuin/wiki/Installation#using-roblox)
  * [Using pesde](https://github.com/LiterallyWize/PNGuin/wiki/Installation#using-pesde)

## [Decoding](https://github.com/LiterallyWize/PNGuin/wiki/Decoding)

  * [Basic Usage](https://github.com/LiterallyWize/PNGuin/wiki/Decoding#basic-usage)
  * [Functions](https://github.com/LiterallyWize/PNGuin/wiki/Decoding#functions)
  * [Types](https://github.com/LiterallyWize/PNGuin/wiki/Decoding#types)

## [Encoding (planned)](https://github.com/LiterallyWize/PNGuin/wiki/Encoding)

### Clone this wiki locally


[ LiterallyWize ](https://github.com/LiterallyWize) / ** [pnguin](https://github.com/LiterallyWize/pnguin) ** Public

  * [ Notifications ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin) You must be signed in to change notification settings
  * [ Fork 0 ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin)
  * [ Star  7 ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin)

# Encoding

Jump to bottom

LiterallyWize edited this page Nov 8, 2025 · [3 revisions](https://github.com/LiterallyWize/pnguin/wiki/Encoding/_history)

### Encoding is currently not supported, planned to be implemented in v3.0.0

[« Previous (Decoding)](https://github.com/LiterallyWize/pnguin/wiki/Decoding)

## [Home](https://github.com/LiterallyWize/pnguin/wiki/Home)

## [Installation](https://github.com/LiterallyWize/pnguin/wiki/Installation)

  * [Using Roblox](https://github.com/LiterallyWize/pnguin/wiki/Installation#using-roblox)
  * [Using pesde](https://github.com/LiterallyWize/pnguin/wiki/Installation#using-pesde)

## [Decoding](https://github.com/LiterallyWize/pnguin/wiki/Decoding)

  * [Basic Usage](https://github.com/LiterallyWize/pnguin/wiki/Decoding#basic-usage)
  * [Functions](https://github.com/LiterallyWize/pnguin/wiki/Decoding#functions)
  * [Types](https://github.com/LiterallyWize/pnguin/wiki/Decoding#types)

## [Encoding (planned)](https://github.com/LiterallyWize/pnguin/wiki/Encoding)

### Clone this wiki locally


[ LiterallyWize ](https://github.com/LiterallyWize) / ** [pnguin](https://github.com/LiterallyWize/pnguin) ** Public

  * [ Notifications ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin) You must be signed in to change notification settings
  * [ Fork 0 ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin)
  * [ Star  7 ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin)

# Installation

Jump to bottom

LiterallyWize edited this page Nov 8, 2025 · [5 revisions](https://github.com/LiterallyWize/pnguin/wiki/Installation/_history)

## Using Roblox

`.rbxm` binaries can be downloaded and imported directly from the [**Releases**](https://github.com/LiterallyWize/PNGuin/releases/latest) page.

## Using [pesde](https://github.com/pesde-pkg/pesde)

[**PNGuin is available as a pesde package**](https://pesde.dev/packages/literallywize/pnguin), and can be installed using the following commands:

    pesde add literallywize/pnguin
    pesde install

[Next (Decoding) »](https://github.com/LiterallyWize/PNGuin/wiki/Decoding)

## [Home](https://github.com/LiterallyWize/PNGuin/wiki/Home)

## [Installation](https://github.com/LiterallyWize/PNGuin/wiki/Installation)

  * [Using Roblox](https://github.com/LiterallyWize/PNGuin/wiki/Installation#using-roblox)
  * [Using pesde](https://github.com/LiterallyWize/PNGuin/wiki/Installation#using-pesde)

## [Decoding](https://github.com/LiterallyWize/PNGuin/wiki/Decoding)

  * [Basic Usage](https://github.com/LiterallyWize/PNGuin/wiki/Decoding#basic-usage)
  * [Functions](https://github.com/LiterallyWize/PNGuin/wiki/Decoding#functions)
  * [Types](https://github.com/LiterallyWize/PNGuin/wiki/Decoding#types)

## [Encoding (planned)](https://github.com/LiterallyWize/PNGuin/wiki/Encoding)

### Clone this wiki locally


[ LiterallyWize ](https://github.com/LiterallyWize) / ** [pnguin](https://github.com/LiterallyWize/pnguin) ** Public

  * [ Notifications ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin) You must be signed in to change notification settings
  * [ Fork 0 ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin)
  * [ Star  7 ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin)

# Decoding

Jump to bottom

LiterallyWize edited this page Nov 8, 2025 · [6 revisions](https://github.com/LiterallyWize/pnguin/wiki/Decoding/_history)

### Basic Usage:

    local PNGuin = require(path.to.PNGuin)

    local png -- Your PNG data

    local pngInfo = PNGuin.Extract(png)
    print(`width: {pngInfo.width}, height: {pngInfo.height}`)

    local pixelData, elapsed = pngInfo:DecodeRGBA()
    print(`decoded in {elapsed} seconds`)

    -- pixelData is now a buffer of RGBA ordered pixel data
    -- e.g. pixelData can be written to an EditableImage

## Functions

### `PNGuin.Extract(input)`

Parses and extracts info from binary PNG data

Parameter | Type | Description
---|---|---
input |  `buffer` / `string` | Input PNG data
Return | Description
**PNGInfo** | Extracted info

### `PNGInfo:DecodeRGBA()`

Decodes pixel data into a `buffer` in RGBA order

Return | Description
`buffer` | RGBA pixel data
`number` | Time taken to decode in seconds

## Types

The below types are subject to change _heavily_ in major versions!

### `PNGInfo`

Property | Type | Values | Description
---|---|---|---
IDAT | `buffer` | N/A | Contains all collected [**IDAT**](https://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.IDAT) chunk data
PLTE | `buffer` | N/A | Contains [**PLTE**](https://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.PLTE) chunk data
tRNS | `buffer?` | N/A | Contains [**tRNS**](https://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.tRNS) chunk data
width | `number` | 0-232 | Width of image (pixels)
height | `number` | 0-232 | Height of image (pixels)
bitDepth | `number` | 1-2-4-8-16 | Bit depth of image from [**IHDR**](https://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.IHDR)
colorType | `ColorType` | **ColorType** | Color type of image from [**IHDR**](https://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.IHDR)
channels | `number` | 1-4 | Number of channels based on color type
DecodeRGBA | `function` | N/A | See **PNGInfo:DecodeRGBA()**
**See code**

        IDAT: buffer,
        PLTE: buffer,
        tRNS: buffer?,

        width: number,
        height: number,

        bitDepth: number,
        colorType: ColorType,
        hasAlpha: boolean,
        channels: number,

        DecodeRGBA: (self: PNGInfo) -> (buffer, number)

### `ColorType`

Type | Values
`string` | Truecolor / Grayscale / Indexed
**See code**

    "Truecolor" | "Grayscale" | "Indexed"

[« Previous (Installation)](https://github.com/LiterallyWize/pnguin/wiki/Installation)  [Next (Encoding) »](https://github.com/LiterallyWize/pnguin/wiki/Encoding)

## [Home](https://github.com/LiterallyWize/pnguin/wiki/Home)

## [Installation](https://github.com/LiterallyWize/pnguin/wiki/Installation)

  * [Using Roblox](https://github.com/LiterallyWize/pnguin/wiki/Installation#using-roblox)
  * [Using pesde](https://github.com/LiterallyWize/pnguin/wiki/Installation#using-pesde)

## [Decoding](https://github.com/LiterallyWize/pnguin/wiki/Decoding)

  * [Basic Usage](https://github.com/LiterallyWize/pnguin/wiki/Decoding#basic-usage)
  * [Functions](https://github.com/LiterallyWize/pnguin/wiki/Decoding#functions)
  * [Types](https://github.com/LiterallyWize/pnguin/wiki/Decoding#types)

## [Encoding (planned)](https://github.com/LiterallyWize/pnguin/wiki/Encoding)

### Clone this wiki locally


[ LiterallyWize ](https://github.com/LiterallyWize) / ** [pnguin](https://github.com/LiterallyWize/pnguin) ** Public

  * [ Notifications ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin) You must be signed in to change notification settings
  * [ Fork 0 ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin)
  * [ Star  7 ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin)

# [History](https://github.com/LiterallyWize/pnguin/wiki/_history) / Home

## Revisions

Compare revisions

  * Update headers

[ LiterallyWize ](https://github.com/LiterallyWize) committed Nov 8, 2025

[460c74f](https://github.com/LiterallyWize/pnguin/wiki/Home/460c74f3868096a087522cbf5be49df47ef110c7)

  * Readd headers

[ LiterallyWize ](https://github.com/LiterallyWize) committed Nov 8, 2025

[79b18af](https://github.com/LiterallyWize/pnguin/wiki/Home/79b18af75f713ce89989da0dd297668ddad62ead)

  * Remove headers

[ LiterallyWize ](https://github.com/LiterallyWize) committed Nov 8, 2025

[7e50c59](https://github.com/LiterallyWize/pnguin/wiki/Home/7e50c594e59e9eed4557a7d39acd5d2587483942)

  * Use unordered lists

[ LiterallyWize ](https://github.com/LiterallyWize) committed Nov 8, 2025

[5cdc33b](https://github.com/LiterallyWize/pnguin/wiki/Home/5cdc33b55b1d2ff2698057cc582d3029115ac142)

  * Fix links and add sidebar

[ LiterallyWize ](https://github.com/LiterallyWize) committed Nov 8, 2025

[1d70a0c](https://github.com/LiterallyWize/pnguin/wiki/Home/1d70a0c5ab8aa9a80ddc5c4e6905321d4c904dc1)

  * Accept and embrace the anchor

[ LiterallyWize ](https://github.com/LiterallyWize) committed Nov 8, 2025

[b875b49](https://github.com/LiterallyWize/pnguin/wiki/Home/b875b498614aa2114122761a6f430173438856ca)

  * Merge attempt to remove the anchor

[ LiterallyWize ](https://github.com/LiterallyWize) committed Nov 8, 2025

[eebbc29](https://github.com/LiterallyWize/pnguin/wiki/Home/eebbc297a5690eec2aea2c14a1eaf48ea2dc137e)

  * Attempt to remove the anchor

[ LiterallyWize ](https://github.com/LiterallyWize) committed Nov 8, 2025

[e2c3d72](https://github.com/LiterallyWize/pnguin/wiki/Home/e2c3d72bb71ba00a39b5a83fb0a4b91e802a91e2)

  * Attempt 2 to remove the anchor

[ LiterallyWize ](https://github.com/LiterallyWize) committed Nov 8, 2025

[95b5d08](https://github.com/LiterallyWize/pnguin/wiki/Home/95b5d08c2d4349bddf560734ffc8f1e8e0e2dc63)

  * Attempt 1 to remove the anchor

[ LiterallyWize ](https://github.com/LiterallyWize) committed Nov 8, 2025

[ebd5276](https://github.com/LiterallyWize/pnguin/wiki/Home/ebd5276444d6b1646f7562b1e919c84368399df1)

  * Fix wiki formatting

[ LiterallyWize ](https://github.com/LiterallyWize) committed Nov 8, 2025

[6aa0760](https://github.com/LiterallyWize/pnguin/wiki/Home/6aa07601335390579a1e8690bcb73220ceb800a4)

  * Create wiki

[ LiterallyWize ](https://github.com/LiterallyWize) committed Nov 8, 2025

[1f084d0](https://github.com/LiterallyWize/pnguin/wiki/Home/1f084d047a85431058d7903852b3e5941de3370d)

  * Initial Home page

[ LiterallyWize ](https://github.com/LiterallyWize) committed Nov 8, 2025

[90327da](https://github.com/LiterallyWize/pnguin/wiki/Home/90327da640aa996bc269a5207cfcc8c8c689889a)


[ LiterallyWize ](https://github.com/LiterallyWize) / ** [pnguin](https://github.com/LiterallyWize/pnguin) ** Public

  * [ Notifications ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin) You must be signed in to change notification settings
  * [ Fork 0 ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin)
  * [ Star  7 ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin)

# Installation

Jump to bottom

LiterallyWize edited this page Nov 8, 2025 · [5 revisions](https://github.com/LiterallyWize/pnguin/wiki/Installation/_history)

## Using Roblox

`.rbxm` binaries can be downloaded and imported directly from the [**Releases**](https://github.com/LiterallyWize/PNGuin/releases/latest) page.

## Using [pesde](https://github.com/pesde-pkg/pesde)

[**PNGuin is available as a pesde package**](https://pesde.dev/packages/literallywize/pnguin), and can be installed using the following commands:

    pesde add literallywize/pnguin
    pesde install

[Next (Decoding) »](https://github.com/LiterallyWize/pnguin/wiki/Decoding)

## [Home](https://github.com/LiterallyWize/pnguin/wiki/Home)

## [Installation](https://github.com/LiterallyWize/pnguin/wiki/Installation)

  * [Using Roblox](https://github.com/LiterallyWize/pnguin/wiki/Installation#using-roblox)
  * [Using pesde](https://github.com/LiterallyWize/pnguin/wiki/Installation#using-pesde)

## [Decoding](https://github.com/LiterallyWize/pnguin/wiki/Decoding)

  * [Basic Usage](https://github.com/LiterallyWize/pnguin/wiki/Decoding#basic-usage)
  * [Functions](https://github.com/LiterallyWize/pnguin/wiki/Decoding#functions)
  * [Types](https://github.com/LiterallyWize/pnguin/wiki/Decoding#types)

## [Encoding (planned)](https://github.com/LiterallyWize/pnguin/wiki/Encoding)

### Clone this wiki locally


[ LiterallyWize ](https://github.com/LiterallyWize) / ** [pnguin](https://github.com/LiterallyWize/pnguin) ** Public

  * [ Notifications ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin) You must be signed in to change notification settings
  * [ Fork 0 ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin)
  * [ Star  7 ](https://github.com/login?return_to=%2FLiterallyWize%2Fpnguin)

# Home

Jump to bottom

LiterallyWize edited this page Nov 8, 2025 · [13 revisions](https://github.com/LiterallyWize/pnguin/wiki/Home/_history)

# Welcome to the [](https://github.com/LiterallyWize/PNGuin)Wiki!

## [Installation](https://github.com/LiterallyWize/pnguin/wiki/Installation)

  * [Using Roblox](https://github.com/LiterallyWize/pnguin/wiki/Installation#using-roblox)
  * [Using pesde](https://github.com/LiterallyWize/pnguin/wiki/Installation#using-pesde)

## [Decoding](https://github.com/LiterallyWize/pnguin/wiki/Decoding)

  * [Basic Usage](https://github.com/LiterallyWize/pnguin/wiki/Decoding#basic-usage)
  * [Functions](https://github.com/LiterallyWize/pnguin/wiki/Decoding#functions)
  * [Types](https://github.com/LiterallyWize/pnguin/wiki/Decoding#types)

## [Encoding (planned)](https://github.com/LiterallyWize/pnguin/wiki/Encoding)

## [Home](https://github.com/LiterallyWize/pnguin/wiki/Home)

## [Installation](https://github.com/LiterallyWize/pnguin/wiki/Installation)

  * [Using Roblox](https://github.com/LiterallyWize/pnguin/wiki/Installation#using-roblox)
  * [Using pesde](https://github.com/LiterallyWize/pnguin/wiki/Installation#using-pesde)

## [Decoding](https://github.com/LiterallyWize/pnguin/wiki/Decoding)

  * [Basic Usage](https://github.com/LiterallyWize/pnguin/wiki/Decoding#basic-usage)
  * [Functions](https://github.com/LiterallyWize/pnguin/wiki/Decoding#functions)
  * [Types](https://github.com/LiterallyWize/pnguin/wiki/Decoding#types)

## [Encoding (planned)](https://github.com/LiterallyWize/pnguin/wiki/Encoding)

### Clone this wiki locally


# Luau Hashing

Pure Luau implementations of several hashing algorithms. Blazing fast (for Luau) and well-tested.

Modules reside in their own folders under `modules` and have their own READMEs with documentation. Additionally, each module is published on [Wally] and [NPM] for convenience.

| Hashing Algorithm              | Wally Dependency    | NPM Dependency       |
|:------------------------------:|:--------------------|:---------------------|
| [`sha-1`](modules/sha1)        | `dekkonot/sha1`     | `@dekkonot/sha1`     |
| [`sha-256`](modules/sha256)    | `dekkonot/sha256`   | `@dekkonot/sha256`   |
| [`sha-224`](modules/sha224)    | `dekkonot/sha224`   | `@dekkonot/sha224`   |
| [`sha-512`](modules/sha512)    | `dekkonot/sha512`   | `@dekkonot/sha512`   |
| [`sha-384`](modules/sha384)    | `dekkonot/sha384`   | `@dekkonot/sha384`   |
| [`xxhash32`](modules/xxhash32) | `dekkonot/xxhash32` | `@dekkonot/xxhash32` |
| [`blake2s`](modules/blake2s)   | `dekkonot/blake2s`  | `@dekkonot/blake2s`  |
| [`blake2b`](modules/blake2b)   | `dekkonot/blake2b`  | `@dekkonot/blake2b`  |
| [`md5`](modules/md5)           | `dekkonot/md5`      | `@dekkonot/md5`      |

[Wally]: https://wally.run/
[NPM]: https://www.npmjs.com/


`tiniest` is a collection of minimal, portable testing libraries for Luau,
built on a few principles:

- Simple, idiomatic Luau throughout.
- Zero external dependencies.
- Small core, extend with plugins.
- Runtime independence.
- No opinions about project structure.

`tiniest` comes with pre-configured bundles for popular runtimes like Roblox and
Lune, or pick and mix your own modules and plugins!

## Features

- `test()` and `describe()` callbacks
- Clean, correctly-cropped stack traces
- Declarative `expect()` API with line numbers and code visualisation
- Pretty-formatted summaries of reports with emoji, Unicode, and colour
- Benchmark how long tests run for
- Extensible, lightweight plugin system
- Snapshot testing for all quotable data types

### Planned

- Test discovery for Lune

## Installation

No installation needed!

`tiniest` is distributed as a set of portable Luau files that sit next to each
other. Drop them into your `lib` folder, keep the ones you need, and start using
`tiniest` right away :)

## Usage

> *🎨 The printed reports come with colour - try it in your terminal!*

Here's an example file written with `tiniest_for_lune`:

```Lua
--!strict

local tiniest = require("@tiniest_for_lune").configure({
    snapshot_path = "./test/__snapshots__",
    save_snapshots = true
})

local function my_test_suite()
    local describe = tiniest.describe
    local test = tiniest.test
    local expect = tiniest.expect
    local snapshot = tiniest.snapshot

    describe("some cool features", function()
        test("it works", function()
            expect(2 + 2).is(4)
        end)

        test("snapshots", function()
            snapshot({
                hello = "world",
                foo = true,
                bar = 2
            })
        end)
    end)
end

local tests = tiniest.collect_tests(my_test_suite)
tiniest.run_tests(tests, {})

The above example generates a report like this and prints it to stdout:

══════════════════════════════ Status of 2 test(s) ═════════════════════════════

✅ some cool features ▸ it works - 74.9µs
✅ some cool features ▸ snapshots - 137.71ms

══════════════════════════════ 2 passed, 0 failed ══════════════════════════════

Time to run: 217.9ms

════════════════════════════════════════════════════════════════════════════════

Failures look like this:

═════════════════════════════ Errors from 2 test(s) ════════════════════════════

❌ some cool features ▸ it works
Expectation not met
   │
16 │ expect(4).is(5)
   │
[string "test/test_main"]:16

❌ some cool features ▸ snapshots
Snapshot does not match
   │
78 │ snapshot({
   │   ["bar"] = 2;
   │   ["foo"] = true;
   │   ["hello"] = "world";
   │ })
   │
   │ -- snapshot on disk:
   │ snapshot({
   │   ["bar"] = 5;
   │   ["foo"] = false;
   │   ["hello"] = "earth";
   │ })
   │
[string "test/test_main"]:20

══════════════════════════════ Status of 2 test(s) ═════════════════════════════

❌ some cool features ▸ it works - 111.6ms
❌ some cool features ▸ snapshots - 174.9ms

══════════════════════════════ 0 passed, 2 failed ══════════════════════════════

Time to run: 292.81ms

════════════════════════════════════════════════════════════════════════════════

## License

Licensed the same way as all of my open source projects: BSD 3-Clause + Security Disclaimer.

As with all other projects, you accept responsibility for choosing and using this project.

See [LICENSE](./LICENSE) or [the license summary](https://github.com/dphfox/licence) for details.


# Dash

A collection of core utilities expanding the capabilities of Luau in Roblox.

[Read the docs](https://roblox.github.io/dash-internal/)

## Usage

Add this package to `dependencies` section in your `rotriever.toml`, for example:

```toml
Dash = "3.3.0"

Then, require anything you need from `Dash`:

```luau
local Dash = require(Packages.Dash)
local map = Dash.map

## Testing

To run tests:

`lest -e tests`

## Benchmarking

To run benchmarks:

`lest -e benchmarks`

## License

Dash is available under the MIT license. See [LICENSE](LICENSE.txt) for details.


# Dash

A collection of core utilities expanding the capabilities of Lua in Roblox.

  * [View the source](https://github.com/Roblox/Dash)
  * [API Reference](https://roblox.github.io/dash-internal/api/)