-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
# Docs Overhauled and redesigned documentation with better pages and documentation. ## Additions - Lifecycle documentation page - State documentation page - Common issues documentation page - Getting started documentation page - Showcase page ## Changes - Overhauled intro page with better examples - Rewrote widgets documentation pages - Updated events documentation pages - Updated copyright ## Fixes - Standardised error messages - Many typos and errors in existing API documentation
- Loading branch information
Showing
24 changed files
with
1,039 additions
and
220 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
* text=auto | ||
* text=auto eol=crlf | ||
*.lua text eol=crlf linguist-language=Luau |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,13 @@ | ||
bin/ | ||
build/ | ||
node_modules/ | ||
.vscode/ | ||
|
||
*.rbxl | ||
*.rbxlx | ||
*.rbxm | ||
*.rbxmx | ||
*.lock | ||
package* | ||
|
||
sourcemap.json |
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"label": "About", | ||
"position": 6 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
--- | ||
sidebar_position: 1 | ||
--- | ||
|
||
# Understanding the Lifecycle | ||
|
||
## General Game Lifecycle | ||
|
||
Iris is designed for games with a core 'game loop' which is the structure that controls what | ||
part of the game process happens when. A typical game loop make look very similar to this: | ||
|
||
```cpp | ||
while(not_closed) { | ||
poll_input(); | ||
update_game_state(); | ||
step_physics(); | ||
render_content(); | ||
wait(); // for a 60 fps limit | ||
} | ||
``` | ||
Here we start firstly with polling for any input changes, since these affect the game state | ||
for that frame. We then update the game state which generally includes the majority of a | ||
game engine, since it would control any user updates, world changes, UI updates and others. | ||
We may also then choose to step our physics engine, assuming we are using a constant frame | ||
rate. Finally we render out everything to our GPU and wait until the appropriate time to | ||
start processing the next frame. | ||
|
||
Roblox takes most of this away from developers, and instead chooses to rely on an event-driven | ||
loop, where we hook onto a part of the engine allowing something else to happen. This makes it | ||
more difficult to use Iris, since not every place we want it will run every frame. However, | ||
Roblox provides access to RunService events, allowing us to execute code every frame, which is | ||
seen below: | ||
|
||
```lua | ||
while not_closed do | ||
update(UserInputService) | ||
update(ContextActionService) | ||
|
||
event(RunService.BindToRenderStepped) | ||
event(RunService.RenderStepped) | ||
|
||
render() | ||
|
||
event(wait) | ||
event(RunService.Stepped) | ||
update(PhysicsService) | ||
|
||
event(RunService.Heartbeat) | ||
update(ReplicationService) | ||
|
||
delay() -- for a 60 fps limit | ||
end | ||
``` | ||
|
||
This is taken from the [Task Scheduler Documentation](https://create.roblox.com/docs/studio/microprofiler/task-scheduler) | ||
which goes into more detail about this. | ||
|
||
## Iris Lifecycle | ||
|
||
Iris needs to run every frame, called the cycle, in order to update global variables and to | ||
clean any unused widgets. This is equivalent to calling `ImGui::EndFrame()` for Dear ImGui, | ||
which would then process the frame buffers ready for use. This order is important for Iris, | ||
which by default uses the `RunService.Heartbeat` event to process this all on. Therefore, for | ||
each frame, any Iris code must run before this event. It is possible to change the event Iris | ||
runs on when initialising, but for most cases, `RunService.Heartbeat` is ideal. | ||
|
||
Understanding this is the key to most effectively using Iris. The library provides a handy | ||
`Iris:Connect()` function which will run any code in the function every frame before the | ||
cycle. This makes it the most convenient. However, any functions provided here will also run | ||
on the initialised event, `RunService.Heartbeat` here, so will run after physics and animations | ||
are calculated. Thankfully, Iris does not constrain you to use only `Iris:Connect()`. You are | ||
able to run Iris code anywhere, in any event, at any time. As long as it is consistent on | ||
every frame, and before the cycle event, it will work properly. Therefore, it is very possible | ||
to put Iris directly into your core game loops. | ||
|
||
## Demonstration | ||
|
||
Say you have a weapon class which is used by every weapon and then also a weapon handler/serivce/system/controller | ||
for handling all weapons on the client. Integrating Iris may look something similar to this: | ||
```lua | ||
------------------------------------------------------------------------ | ||
--- game.ReplicatedStorage.Modules.Client.Weaopns.WeaponsService.lua | ||
------------------------------------------------------------------------ | ||
local WeaponsService = { | ||
maxWeapons = 10, | ||
activeWeapon = nil, | ||
weapons = {} | ||
} | ||
|
||
function WeaponsService.init() | ||
end | ||
|
||
-- called every frame to update all weapons | ||
function WeaponsService.update(deltaTime: number) | ||
Iris.Window({ "Weapons Service" }) | ||
|
||
WeaponsService.doSomething() | ||
Iris.CollapsingHeader({ "Global Variables" }) | ||
Iris.DragNum({ "Max Weapons", 1, 0 }, { number = Iris.TableState(WeaponsService.maxWeapons) }) | ||
Iris.End() | ||
|
||
Iris.CollapsingHeader({ "Weapons" }) | ||
Iris.Tree({ `Active Weapon: {WeaponsService.activeWeapon.name}` }) | ||
WeaponsService.activeWeapon:update() | ||
Iris.End() | ||
|
||
Iris.SeparatorText({ "All Weapons" }) | ||
for _, weapon: weapon in WeaponsService.weapons do | ||
Iris.Tree({ weapon.name }) | ||
weapon:update() | ||
Iris.End() | ||
end | ||
Iris.End() | ||
|
||
WeaponsService.doSomethingElse() | ||
Iris.End() | ||
end | ||
|
||
function WeaponsService.terminate() | ||
end | ||
|
||
return WeaponsService | ||
|
||
------------------------------------------------------------------------ | ||
--- game.ReplicatedStorage.Modules.Client.Weaopns.Weapon.lua | ||
------------------------------------------------------------------------ | ||
local Weapon = {} | ||
Weapon.__index = Weapon | ||
|
||
function Weapon.new(...) | ||
end | ||
|
||
function Weapon.update(self, deltaTime: number) | ||
Iris.Text({ `ID: {self.id}` }) | ||
Iris.Text({ `Bullets: {self.bullets}/{self.capacity}" }) | ||
Iris.Checkbox({ "No reload" }, { isChecked = Iris.TableState(self.noreload) }) | ||
... | ||
self:updateInputs() | ||
self:updateTransforms() | ||
... | ||
end | ||
|
||
function Weapon.destroy(self) | ||
end | ||
|
||
``` | ||
|
||
Although this is very bare bones code, we are not using any `Iris:Connect()` methods | ||
and instead place our Iris code directly in our update events which we know will run | ||
every frame. Another practice this shows is starting a window somewhere and keeping it | ||
open through all weapons before closing it and the end of the update. Therefore, we | ||
can place lots of different widgets in one window and keep everything organised. | ||
|
||
The showcase by [@Boogle](https://x.com/LeBoogle/status/1772384187426709879) shows | ||
off Iris used exactly like this, but with an actual working system. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
--- | ||
sidebar_position: 3 | ||
--- | ||
|
||
# Understanding State | ||
|
||
An Iris State object is simply a table containg a value, and an array of connected widgets. It provides functions to | ||
get or set the value, the latter of which will update any widgets UI that are dependent on that state. Functions can | ||
also be connected which will be fired whenever the value changes. | ||
|
||
A state object ultimately attempts to copy the behaviour that a pointer would do in other languages, but is not | ||
possible in native Luau. A Luau table is the best option, because it is passed by reference. | ||
|
||
## Types of State | ||
|
||
Iris provides multiple different types of State objects, suited for different needs. | ||
|
||
### State | ||
|
||
The base and most common state type, which implements the basically functionality of any state object. | ||
|
||
### WeakState | ||
|
||
A WeakState is very similar to State, except that every time it is called by ID, using `Iris.WeakState()`, all | ||
connected widgets and functions are removed, whilst keeping the value. This is useful if you need to disconnect any | ||
widgets from a state, so that they no longer update, whilst also keeping the existing value. | ||
|
||
### VariableState | ||
|
||
A VariableState takes both a value, and a function which gives the new value of the state whenever it is changed. This | ||
is designed for when you have a variable within a file, and want to link it to a state object. By default, when the | ||
function is called, if the variable and state are different, it will choose the local variable value. But if the state | ||
is changed, it will use the callback which is designed to update the local variable. | ||
|
||
This is best shown with an example: | ||
```lua | ||
local myNumber = 5 | ||
|
||
local state = Iris.VariableState(myNumber, function(value) | ||
myNumber = value | ||
end) | ||
Iris.DragNum({ "My number" }, { number = state }) | ||
``` | ||
|
||
Here we create a state for a DragNum. If we update the value of `myNumber` within the code earlier, it will update the | ||
state value. And if we drag the widget, and update the state, it will call our callback, where we update the value of | ||
`myNumber`. | ||
|
||
### TableState | ||
|
||
A TableState acts like VariableState, but takes a table and index so that whenever the table value changes, the state | ||
changes and vice versa. Because tables are shared, we do not need to provide a function to update the table value, and | ||
is instead handled internally. | ||
|
||
We can see this with an example: | ||
```lua | ||
local data = { | ||
myNumber = 5 | ||
} | ||
|
||
local state = Iris.TableState(data, "myNumber") | ||
Iris.DragNum({ "My number" }, { number = state }) | ||
``` | ||
|
||
A third argment provides extra functionality, allowing us to call a function before updating the table value, which can | ||
be used when we need to change some other values, for example when enabling or disabling a class. | ||
|
||
### ComputedState | ||
|
||
ComputedState takes an existing state and a function which will convert the value of one state to a new one. We can use | ||
this to ensure that a state always stays dependent on another state. |
Oops, something went wrong.