Skip to content
This repository has been archived by the owner on Sep 22, 2024. It is now read-only.

Commit

Permalink
Tutorials!
Browse files Browse the repository at this point in the history
  • Loading branch information
metatablecat committed Apr 25, 2024
1 parent ba861f2 commit fc8a501
Show file tree
Hide file tree
Showing 7 changed files with 635 additions and 0 deletions.
206 changes: 206 additions & 0 deletions docs/tutorials/getting-started/basic-fragment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
# Basic Fragment

Lets go into a basic fragment, and explain how the model works.

## Importing Catwork

Each Fragment should first import Catwork.

```lua
local ReplicatedFirst = game:GetService("ReplicatedFirst")
local Catwork = require(ReplicatedFirst.Catwork)
```

!!! note
This tutorial expects Catwork to be in `ReplicatedFirst`, make sure its
there before following this tutorial

Future tutorials will assume you're importing this.

## A clock counter

For this tutorial, we're going to slowly build an API for interfacing with a
simple real-time clock system.

Add `Lighting` as an import after your `Catwork` import:

```lua
local Lighting = game:GetService("Lighting") -- required for this tutorial
```

### Adding Basic Logic

Lets create a basic Fragment that iterates the current Lighting time. To do
this, we call the `Catwork.Fragment` constructor:

```lua
return Catwork.Fragment {
Name = "LightingClockTime",

Init = function(self)
while true do
task.wait(1)
Lighting.ClockTime += 1/60
end
end,
}
```

If you run this script, you'll notice the clock slowly ticks forward.

??? bug "Not Working?"
Here's some possible reasons for your code not working.

1. You've not imported Catwork, Lighting or ReplicatedFirst.
2. You didn't include a Runtime, refer to Installation for that.
3. There's a typo in your script.

Lets explain how this constructor works.

First, we give it the name `LightingClockTime`, this doesn't do much internally
as Fragments use a different method for being uniquely identified, however, it
helps us identify which Fragment we're working with.

```lua
Name = "LightingClockTime
```

After that, we add an Init callback, which indicates what should happen when the
Fragment is ready to go.

```lua
Init = function(self)
while true do
task.wait(1)
Lighting.ClockTime += 1/60
end
end,
```

### Adding an API

The `Catwork.Fragment` constructor lets you define any logic you want to on the
object, and have it passed to other code using it.

Lets add a API to get the current clock time, and set a timezone offset. To do
this, add two new methods directly to the constructor

```lua hl_lines="11-17"
return Catwork.Fragment {
Name = "LightingClockTime",

Init = function(self)
while true do
task.wait(1)
Lighting.ClockTime += 1/60
end
end,

GetClockTime = function(self)

end,

SetTimeZoneOffset = function(self, offset)

end
}
```

We're also going to add an in-built timer, and offset, to the fragment as a property:

```lua hl_lines="4 5"
return Catwork.Fragment {
Name = "LightingClockTime",

Time = os.time(),
TimeZoneOffset = 0
```

Now, lets implement our two new methods, firstly, `GetClockTime`. This method
should just return `self.Time`:

```lua
GetClockTime = function(self)
return self.Time
end
```

And, for `SetTimeZoneOffset`, this should change `self.TimeZoneOffset`

```lua
SetTimeZoneOffset = function(self, offset)
self.TimeZoneOffset = offset
end
```

### Updating Init

If you run the project, and interface with the Fragment, you may notice that nothing
happens. This is because we haven't updated our `Init` callback.

Here's the updated Init callback:
```lua
Init = function(self)
while true do
Lighting.ClockTime = ((self.Time / 3600) % 24) + self.TimeZoneOffset
task.wait(1)
end
end
```

If you run the game now, the time in-game should match near to your local
computer time. (If you computer is set to UTC.).

## Using the API

If you used a ModuleScript, you can now require your script within *another* script,
and interface with the clock counter. Here's a script that requires in the ClockCounter
script, and prints out the clock time every second:

```lua
local ClockCounter = require(path.to.ClockCounter)

while true do
print(ClockCounter:GetClockTime())
task.wait(1)
end
```

## Asynchronous design

Catwork utilises a simple asynchronous dependency system, through `Fragment:Await`
and `Fragment:HandleAsync`.

This works by waiting until a Fragment's `Init` function completes (returns) then
resumes any code waiting for it complete. You may notice an issue with our
`Init` callback, in that it **never** returns, and so any code waiting on it will
also never resume.

Lets fix our Init callback to address this. We're going to use `task.spawn` to
create a new thread within our `Init` callback, that runs independently of any
code waiting upon it.

```lua
Init = function(self)
task.spawn(function()
while true do
Lighting.ClockTime = ((self.Time / 3600) % 24) + self.TimeZoneOffset
task.wait(1)
end
end)
end
```

You should always `Await`/`HandleAsync` on Fragments, because you cannot guarantee
that they are ready. The Service tutorial explains the Fragment:Spawn lifecycle
more in depth.

```lua hl_lines="2"
local ClockCounter = require(path.to.ClockCounter)
ClockCounter:Await()

while true do
print(ClockCounter:GetClockTime())
task.wait(1)
end
```
110 changes: 110 additions & 0 deletions docs/tutorials/getting-started/installation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Installation

Catwork can be installed either as a Roblox model, or within a larger project
inside your editor of choice.


---

The following guide is different for within Roblox Studio, and within external editors.

=== "Roblox Studio"

The RBXM can be obtained below. Its best to drag the module into `ReplicatedFirst`,
unless you intend to use the tool as a plugin (which has its own section.)

The tutorials expect Catwork to be placed in `ReplicatedFirst`.

[:fontawesome-solid-cat: Download Catwork](https://github.com/metatablecatgames/catwork/releases/download/v0.4.4/catwork.rbxm){ .md-button .md-button--primary}

=== "External"

If you intend to use Catwork externally, the sourcecode can be found on the
Releases page on the GitHub repository

[GitHub Releases](https://github.com/metatablecatgames/catwork/releases/){ .md-button .md-button--primary}

!!! info "Expected for Studio"
The tutorials expect you to use Catwork within Studio, but can somewhat
be followed in your editor of choice, if you setup the project correctly.

## Obtaining a Runtime

Catwork does not come bundled with a Runtime, which is an intentional choice for the time being.

!!! abstract "CollectionService Runtime (RECOMENDED)"
This runtime loads ModuleScripts with a given tag based on the context it is running
in.

=== "Game Context"

```lua
local CollectionService = game:GetService("CollectionService")
local RunService = game:GetService("RunService")

local TAG_SHARED = "SharedFragment"
local TAG_LOCAL = if RunService:IsClient() then "ClientFragment" else "ServerFragment"
local passed, failed = 0, 0

local function safeRequire(module)
local success, result = pcall(require, module)
if success then
passed += 1
return result
else
warn("Error when requiring", module, ":", result)
failed += 1
return nil
end
end

local function loadGroup(tag)
local m = CollectionService:GetTagged(tag)
for _, mod in m do
if mod:IsA("ModuleScript") then
safeRequire(mod)
end
end
end
local t = os.clock()
loadGroup(TAG_LOCAL)
loadGroup(TAG_SHARED)
local f = os.clock() - t

print(`🐈 CatworkRun. {passed} modules required, {failed} modules failed. Load time: {math.round(f * 1000)}ms`)
```

=== "Plugin Context"

```lua
local CollectionService = game:GetService("CollectionService")
if not plugin then return end

local TAG = "PluginFragment"

local function safeRequire(module)
local success, result = pcall(require, module)
if success then
return result
else
warn("Error when requiring", module, ":", result)
return nil
end
end

local function loadGroup(tag)
local m = CollectionService:GetTagged(tag)
for _, mod in m do
if mod:IsDescendentOf(plugin) and mod:IsA("ModuleScript") then
safeRequire(mod)
end
end
end

loadGroup(TAG)
```

You should generally use this runtime where possible as it's configured from CollectionService tags, and doesn't
require you to fiddle with the script.

12 changes: 12 additions & 0 deletions docs/tutorials/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Tutorials

The following section introduces Catwork in an easy-to-digest and learn manner.

!!! note
These tutorials are designed so you can jump around as you need to for different
concepts, but build on ideas learnt in previous sections.

If you get stuck, feel free to send me a
[message on the DevForum](https://devforum.roblox.com/u/metatablecatmaid), or
get help from the [Roblox Open Source community](https://discord.gg/Qm3JNyEc32)
discord server!
Loading

0 comments on commit fc8a501

Please sign in to comment.