Skip to content

Commit

Permalink
release 2.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
jackdotink committed Oct 4, 2023
1 parent ffd21a7 commit 6fc9768
Show file tree
Hide file tree
Showing 13 changed files with 308 additions and 11 deletions.
21 changes: 20 additions & 1 deletion Test/Client/Client.client.luau
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local ComplexFunction = require(ReplicatedStorage.ComplexFunction)
local EmptyFunction = require(ReplicatedStorage.EmptyFunction)
local SimpleFunction = require(ReplicatedStorage.SimpleFunction)

local ComplexEvent = require(ReplicatedStorage.ComplexEvent):Client()
local SimpleEvent = require(ReplicatedStorage.SimpleEvent):Client()
local EmptyEvent = require(ReplicatedStorage.EmptyEvent):Client()
local ReadyEvent = require(ReplicatedStorage.ReadyEvent):Client()

task.wait(0.5)

print("Registering Listeners")

ComplexEvent:On(function(Value1, Value2, Value3)
print("ComplexEvent", Value1, Value2, Value3)
end)
Expand All @@ -18,12 +25,24 @@ EmptyEvent:On(function()
print("EmptyEvent")
end)

task.wait(0.5)

print("Firing Events")

ComplexEvent:Fire({ one = { "String Literal", 123 }, two = { 123, "String Literal" } }, "hello world again", 123)
SimpleEvent:Fire(123)
EmptyEvent:Fire()

task.wait(1)
task.wait(0.5)

print("Calling Functions")

print(ComplexFunction:Call({ one = { "String Literal", 123 }, two = { 123, "String Literal" } }, "hi", 12):Await())
print(SimpleFunction:Call(123):Await())
print(EmptyFunction:Call():Await())

task.wait(0.5)

print("Firing Ready")

ReadyEvent:Fire()
28 changes: 27 additions & 1 deletion Test/Server/Server.server.luau
Original file line number Diff line number Diff line change
@@ -1,11 +1,37 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local ComplexFunction = require(ReplicatedStorage.ComplexFunction)
local SimpleFunction = require(ReplicatedStorage.SimpleFunction)
local EmptyFunction = require(ReplicatedStorage.EmptyFunction)

local ComplexEvent = require(ReplicatedStorage.ComplexEvent):Server()
local SimpleEvent = require(ReplicatedStorage.SimpleEvent):Server()
local EmptyEvent = require(ReplicatedStorage.EmptyEvent):Server()
local ReadyEvent = require(ReplicatedStorage.ReadyEvent):Server()

print("Registering Listeners")
print("Registering Function Callbacks")

ComplexFunction:SetCallback(function(Player, Value1, Value2, Value3)
print("ComplexFunction", Player, Value1, Value2, Value3)
task.wait(0.2)

return Value1, Value2, Value3
end)

SimpleFunction:SetCallback(function(Player, Value)
print("SimpleFunction", Player, Value)

return tostring(Value)
end)

EmptyFunction:SetCallback(function(Player)
print("EmptyFunction", Player)

return
end)

print("Registering Event Listeners")

ComplexEvent:On(function(Player, Value1, Value2, Value3)
print("ComplexEvent", Player, Value1, Value2, Value3)
end)
Expand Down
13 changes: 13 additions & 0 deletions Test/Shared/ComplexFunction.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Red = require(ReplicatedStorage.Packages.Red)
local Guard = require(ReplicatedStorage.Packages.Guard)

local ValueCheck =
Guard.Optional(Guard.Map(Guard.String, Guard.List(Guard.Or(Guard.Literal("String Literal"), Guard.Number))))

return Red.Function("ComplexFunction", function(Value1, Value2, Value3)
return ValueCheck(Value1), Guard.String(Value2), Guard.Number(Value3)
end, function(Value1, Value2, Value3)
return ValueCheck(Value1), Guard.String(Value2), Guard.Number(Value3)
end)
9 changes: 9 additions & 0 deletions Test/Shared/EmptyFunction.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Red = require(ReplicatedStorage.Packages.Red)

return Red.Function("EmptyFunction", function()
return
end, function()
return
end)
10 changes: 10 additions & 0 deletions Test/Shared/SimpleFunction.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Red = require(ReplicatedStorage.Packages.Red)
local Guard = require(ReplicatedStorage.Packages.Guard)

return Red.Function("SimpleFunction", function(Value)
return Guard.Number(Value)
end, function(Value)
return Guard.String(Value)
end)
14 changes: 7 additions & 7 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@ function nav() {
return [
{ text: 'FAQ', link: '/faq' },
{ text: 'Guide', link: '/guide/introduction/what-is-red' },
{
text: 'API Reference',
items: [
{ text: '2.0', link: '/2.0/Red' },
]
},
{ text: 'API Reference', link: '/2.0/Red' },
]
}

Expand All @@ -32,6 +27,10 @@ function sidebar() {
{ text: 'Client Usage', link: '/guide/events/client' },
]
},
{
text: 'Functions',
link: '/guide/functions',
},
],

'/2.0/': [
Expand All @@ -46,7 +45,8 @@ function sidebar() {
{ text: 'Server', link: '/2.0/Event/Server' },
{ text: 'Client', link: '/2.0/Event/Client' },
]
}
},
{ text: 'Function', link: '/2.0/Function' },
],
}
],
Expand Down
45 changes: 45 additions & 0 deletions docs/2.0/Function.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Function

Functions allow RemoteFunction-like behavior in Red for calling client -> server.

## SetCallback

Sets the function's callback.

```lua
<A..., R...>(
Callback: (Player, A...) -> R... -- The callback to set.
)
```

A singular callback must be set on the server to allow clients to call the function. This callback is given the arguments and must return the expected return values. This callback may only be set on the server, attempting to set it on the client will result in an error.

```lua
local Function = require(Path.To.Function)

Function:SetCallback(function(Player, Arg1, Arg2, Arg3)
return Arg1, Arg2, Arg3
end)
```

::: danger
If the callback errors the client will never recieve any value and will yield forever. **Doing this is a memory leak!** Do not rely on erroring not sending back values.
:::

## Call

Calls the function on the server.

```lua
<A..., R...>(
...A: any -- The arguments to pass to the function.
): Future<R...>
```

A function is called on the client to call the function on the server. This method returns a [Future](https://util.redblox.dev/future) which can be used to await the return values or connect a function to be called when the return values are received.

```lua
local Function = require(Path.To.Function)

local Ret1, Ret2, Ret3 = Function:Call(Arg1, Arg2, Arg3):Await()
```
36 changes: 36 additions & 0 deletions docs/2.0/Red.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,39 @@ end)
::: tip
It is recommended to use [Guard](https://util.redblox.dev/guard) to typecheck payloads as it also narrows types.
:::

## Function

Creates a new [function](./Function) object.

```lua
<A..., R...>(
Name: string, -- The name of the function
ValidateArg: (...unknown) -> A..., -- Validates function arguments
ValidateRet: (...unknown) -> R..., -- Validates function returns
) -> Function<A..., R...>
```

This will create a function with the passed name.

::: danger
The name of the function must be unique, using the same name twice will result in an error.
:::

The validation functions are used to validate the type of the arguments and returns. The functions have the same three rules as event validation functions:

1. The callback returns the arguments in the same order they were passed in.
2. The callback must error if the arguments are invalid.
3. The callback must narrow the types of the arguments.

```lua
return Red.Function("FunctionName", function(Number, String)
return Guard.Number(Number), Guard.String(String)
end, function(Number, String)
return Guard.Number(Number), Guard.String(String)
end)
```

::: tip
It is recommended to use [Guard](https://util.redblox.dev/guard) to typecheck as it also narrows types.
:::
83 changes: 83 additions & 0 deletions docs/guide/functions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Functions

Functions are Red's version of RemoteFunctions. If you don't know what they are, it's a way to call code on the server from the client and get return values. Red only allows clients to call the server, not the server to call clients.

## Declaring

Functions are declared similarly to events, they take two validation callbacks: one for the arguments and one for the return value.

```lua
local Red = require(Path.To.Red)

return Red.Function("FunctionName", function(Arg1, Arg2, Arg3)
assert(type(Arg1) == "string")
assert(type(Arg2) == "number")
assert(type(Arg3) == "boolean")

return Arg1, Arg2, Arg3
end, function(Ret1, Ret2, Ret3)
assert(type(Ret1) == "string")
assert(type(Ret2) == "number")
assert(type(Ret3) == "boolean")

return Ret1, Ret2, Ret3
end)
```

These callbacks must follow the same three rules that event validation callbacks do:

1. The callback returns the arguments in the same order they were passed in.
2. The callback must error if the arguments are invalid.
3. The callback must narrow the types of the arguments.

These callbacks are only called in specific circumstances. Do not use these callbacks as middleware, logging, or other side effects.

::: tip
I once again suggest using [Guard](https://util.redblox.dev/guard) to both narrow and check types at the same time.

```lua
local Red = require(Path.To.Red)
local Guard = require(Path.To.Guard)

local CheckArg1 = Guard.Map(Guard.String, Guard.Number)
local CheckArg2 = Guard.List(Guard.Vector3)
local CheckArg3 = Guard.Boolean

local CheckRet1 = Guard.String
local CheckRet2 = Guard.Number
local CheckRet3 = Guard.Set(Guard.String)

return Red.Function("FunctionName", function(Arg1, Arg2, Arg3)
return CheckArg1(Arg1), CheckArg2(Arg2), CheckArg3(Arg3)
end, function(Ret1, Ret2, Ret3)
return CheckRet1(Ret1), CheckRet2(Ret2), CheckRet3(Ret3)
end)
```

:::

## Set Callback

A singular callback must be set on the server to allow clients to call the event. This callback is given the arguments and must return the expected return values.

```lua
local Function = require(Path.To.Function)

Function:SetCallback(function(Player, Arg1, Arg2, Arg3)
return Arg1, Arg2, Arg3
end)
```

::: danger
If the callback errors then the client will never recieve any value and will yield forever. **Doing this is a memory leak!** Do not rely on erroring not sending back values.
:::

## Calling

Functions can only be called from the client. The client must pass valid arguments to the function, and will be given back a [Future](https://util.redblox.dev/future) that completes with the returned values.

```lua
local Function = require(Path.To.Function)

local Ret1, Ret2, Ret3 = Function:Call("Hello", 1, true):Await()
```
2 changes: 1 addition & 1 deletion lib/Event/init.luau
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ local function Client<T...>(self: Event<T...>): ClientEvent.Client<T...>
end

local function Event<T...>(Name: string, Validate: (...unknown) -> T...): Event<T...>
assert(not Identifier.Exists(Name), "Cannot create event with duplicate name!")
assert(not Identifier.Exists(Name), "Cannot use same name twice")

return {
Id = Identifier.Shared(Name):Await(),
Expand Down
55 changes: 55 additions & 0 deletions lib/Function.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
local RunService = game:GetService("RunService")

local Future = require(script.Parent.Parent.Future)

local ServerEvent = require(script.Parent.ServerEvent)
local ClientEvent = require(script.Parent.ClientEvent)
local Identifier = require(script.Parent.Identifier)

local function PackArgs(...: any)
return { ... }
end

export type Function<A..., R...> = {
Id: string,
Validate: (...unknown) -> A...,
Listening: boolean,

SetCallback: (self: Function<A..., R...>, Callback: (Player, A...) -> R...) -> (),
Call: (self: Function<A..., R...>, A...) -> typeof(Future.new(function(): R... end)),
}

local function SetCallback<A..., R...>(self: Function<A..., R...>, Callback: (Player, A...) -> R...)
assert(RunService:IsServer(), "Cannot set callback to function on client")
assert(not self.Listening, "Cannot set callback to function multiple times")

self.Listening = true
ServerEvent.Listen(self.Id, function(Player, ...)
if pcall(self.Validate, ...) then
return Callback(Player, ...)
end
end)
end

local function Call<A..., R...>(self: Function<A..., R...>, ...: A...)
return ClientEvent.Call(self.Id, PackArgs(...))
end

local function Function<A..., R...>(
Name: string,
ValidateArg: (...unknown) -> A...,
ValidateRet: (...unknown) -> R...
): Function<A..., R...>
assert(not Identifier.Exists(Name), "Cannot use same name twice")

return {
Id = Identifier.Shared(Name):Await(),
Validate = ValidateArg,
Listening = false,

SetCallback = SetCallback,
Call = Call,
} :: any
end

return Function
1 change: 1 addition & 0 deletions lib/init.luau
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ end

return {
Event = require(script.Event),
Function = require(script.Function),
}
Loading

0 comments on commit 6fc9768

Please sign in to comment.