Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add manual event loop option #47

Merged
merged 8 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/.vitepress/components/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ const beforeMount = (monaco: Monaco) => {

const Calls = ["SingleSync", "SingleAsync", "ManySync", "ManyAsync"] as const;

const Options = ["typescript", "write_checks", "casing", "server_output", "client_output"] as const;
const Options = ["typescript", "write_checks", "casing", "server_output", "client_output", "manual_event_loop"] as const;

const Casing = ["PascalCase", "camelCase", "snake_case"].map((value) => `"${value}"`);

Expand Down Expand Up @@ -151,8 +151,11 @@ const beforeMount = (monaco: Monaco) => {
opt: Options,

casing: Casing,

typescript: Operators,
write_checks: Operators,
manual_event_loop: Operators,

output_server: [],
output_client: [],
} as const;
Expand Down
45 changes: 45 additions & 0 deletions docs/config/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,48 @@ When enabled, Zap will generate a `.d.ts` file for the server and client with th
### Example

<CodeBlock code="opt typescript = true" />

## `manual_event_loop`

This option determines if Zap automatically sends reliable events each Heartbeat.

When enabled, a `SendEvents` function will be exported from the client and server modules that must be called manually.

This is useful when you can easily run `SendEvents` after all events have been fired each frame.

::: danger
At the time of writing (January 2024), Roblox has an issue where firing remotes at too high of a rate (above 60 hz) can cause the server to have incredibly high network response times.

**This causes servers to essentially crash, and all clients to disconnect.**

This can be mitigated by firing remotes to the server at a timed rate, so as to not exceed 60 hz.

```lua
local Timer = 0

RunService.Heartbeat:Connect(function(DeltaTime)
Timer += DeltaTime

-- Only send events 60 times per second
if Timer >= 1 / 60 then
Timer = 0
Zap.SendEvents()
end
end)
```

Note that Zap uses `RunService.Heartbeat` and a 61 hz rate by default.
:::

### Default

`false`

### Options

- `true`
- `false`

### Example

<CodeBlock code="opt manual_event_loop = true" />
1 change: 1 addition & 0 deletions zap/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub struct Config<'src> {

pub write_checks: bool,
pub typescript: bool,
pub manual_event_loop: bool,

pub server_output: &'src str,
pub client_output: &'src str,
Expand Down
20 changes: 0 additions & 20 deletions zap/src/output/luau/client.luau
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,3 @@ assert(unreliable:IsA("UnreliableRemoteEvent"), "Expected ZAP_UNRELIABLE to be a
local event_queue: { [number]: { any } } = {}

local time = 0

RunService.Heartbeat:Connect(function(dt)
time += dt

if time >= (1 / 61) then
time -= (1 / 61)

if outgoing_used ~= 0 then
local buff = buffer.create(outgoing_used)
buffer.copy(buff, 0, outgoing_buff, 0, outgoing_used)

reliable:FireServer(buff, outgoing_inst)

outgoing_buff = buffer.create(64)
outgoing_used = 0
outgoing_size = 64
table.clear(outgoing_inst)
end
end
end)
53 changes: 53 additions & 0 deletions zap/src/output/luau/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,51 @@ impl<'src> ClientOutput<'src> {
}
}

fn push_event_loop(&mut self) {
self.push("\n");

if self.config.manual_event_loop {
let send_events = self.config.casing.with("SendEvents", "sendEvents", "send_events");
sasial-dev marked this conversation as resolved.
Show resolved Hide resolved

self.push_line(&format!("local function {send_events}()"));
self.indent();
} else {
self.push_line("RunService.Heartbeat:Connect(function(dt)");
self.indent();
self.push_line("time += dt");
self.push("\n");
self.push_line("if time >= (1 / 61) then");
self.indent();
self.push_line("time -= (1 / 61)");
self.push("\n");
}

self.push_line("if outgoing_used ~= 0 then");
self.indent();
self.push_line("local buff = buffer.create(outgoing_used)");
self.push_line("buffer.copy(buff, 0, outgoing_buff, 0, outgoing_used)");
self.push("\n");
self.push_line("reliable:FireServer(buff, outgoing_inst)");
self.push("\n");
self.push_line("outgoing_buff = buffer.create(64)");
self.push_line("outgoing_used = 0");
self.push_line("outgoing_size = 64");
self.push_line("table.clear(outgoing_inst)");
self.dedent();
self.push_line("end");
self.dedent();

if self.config.manual_event_loop {
self.push_line("end");
} else {
self.push_line("end");
self.dedent();
self.push_line("end)");
}

self.push("\n");
}

fn push_reliable_header(&mut self) {
self.push_line("reliable.OnClientEvent:Connect(function(buff, inst)");
self.indent();
Expand Down Expand Up @@ -476,6 +521,12 @@ impl<'src> ClientOutput<'src> {
self.push_line("return {");
self.indent();

if self.config.manual_event_loop {
let send_events = self.config.casing.with("SendEvents", "sendEvents", "send_events");

self.push_line(&format!("{send_events} = {send_events},"));
}

self.push_return_outgoing();
self.push_return_listen();

Expand All @@ -495,6 +546,8 @@ impl<'src> ClientOutput<'src> {

self.push_tydecls();

self.push_event_loop();

self.push_callback_lists();

if self
Expand Down
16 changes: 0 additions & 16 deletions zap/src/output/luau/server.luau
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,3 @@ end
Players.PlayerRemoving:Connect(function(player)
player_map[player] = nil
end)

RunService.Heartbeat:Connect(function()
for player, outgoing in player_map do
if outgoing.used > 0 then
local buff = buffer.create(outgoing.used)
buffer.copy(buff, 0, outgoing.buff, 0, outgoing.used)

reliable:FireClient(player, buff, outgoing.inst)

outgoing.buff = buffer.create(64)
outgoing.used = 0
outgoing.size = 64
table.clear(outgoing.inst)
end
end
end)
48 changes: 48 additions & 0 deletions zap/src/output/luau/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,46 @@ impl<'a> ServerOutput<'a> {
}
}

fn push_event_loop(&mut self) {
self.push("\n");

if self.config.manual_event_loop {
let send_events = self.config.casing.with("SendEvents", "sendEvents", "send_events");

self.push_line(&format!("local function {send_events}()"));
} else {
self.push_line("RunService.Heartbeat:Connect(function()");
}

self.indent();
self.push_line("for player, outgoing in player_map do");
self.indent();
self.push_line("if outgoing.used > 0 then");
self.indent();
self.push_line("local buff = buffer.create(outgoing.used)");
self.push_line("buffer.copy(buff, 0, outgoing.buff, 0, outgoing.used)");
self.push("\n");
self.push_line("reliable:FireClient(player, buff, outgoing.inst)");
self.push("\n");
self.push_line("outgoing.buff = buffer.create(64)");
self.push_line("outgoing.used = 0");
self.push_line("outgoing.size = 64");
self.push_line("table.clear(outgoing.inst)");
self.dedent();
self.push_line("end");
self.dedent();
self.push_line("end");
self.dedent();

if self.config.manual_event_loop {
self.push_line("end");
} else {
self.push_line("end)");
}
sasial-dev marked this conversation as resolved.
Show resolved Hide resolved

self.push("\n");
}

fn push_reliable_header(&mut self) {
self.push_line("reliable.OnServerEvent:Connect(function(player, buff, inst)");
self.indent();
Expand Down Expand Up @@ -539,6 +579,12 @@ impl<'a> ServerOutput<'a> {
self.push_line("return {");
self.indent();

if self.config.manual_event_loop {
let send_events = self.config.casing.with("SendEvents", "sendEvents", "send_events");

self.push_line(&format!("{send_events} = {send_events},"));
}

self.push_return_outgoing();
self.push_return_listen();

Expand All @@ -558,6 +604,8 @@ impl<'a> ServerOutput<'a> {

self.push_tydecls();

self.push_event_loop();

self.push_callback_lists();

if self
Expand Down
4 changes: 4 additions & 0 deletions zap/src/output/typescript/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ impl<'src> ClientOutput<'src> {
return self.buf;
};

if self.config.manual_event_loop {
self.push_manual_event_loop(self.config);
}

self.push_tydecls();

self.push_return();
Expand Down
11 changes: 7 additions & 4 deletions zap/src/output/typescript/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::config::{Enum, Ty};
use crate::config::{Config, Enum, Ty};

pub mod client;
pub mod server;
Expand Down Expand Up @@ -186,8 +186,11 @@ pub trait Output {
"// {scope} generated by Zap v{} (https://github.com/red-blox/zap)",
env!("CARGO_PKG_VERSION")
));
self.push("\n");
self.push_line("export {};");
self.push("\n");
}

fn push_manual_event_loop(&mut self, config: &Config) {
let send_events = config.casing.with("SendEvents", "sendEvents", "send_events");

self.push_line(&format!("export const {send_events}: () => void"))
}
}
4 changes: 4 additions & 0 deletions zap/src/output/typescript/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ impl<'a> ServerOutput<'a> {
return self.buf;
};

if self.config.manual_event_loop {
self.push_manual_event_loop(self.config);
}

self.push_tydecls();

self.push_return();
Expand Down
2 changes: 2 additions & 0 deletions zap/src/parser/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ impl<'src> Converter<'src> {

let (write_checks, ..) = self.boolean_opt("write_checks", true, &config.opts);
let (typescript, ..) = self.boolean_opt("typescript", false, &config.opts);
let (manual_event_loop, ..) = self.boolean_opt("manual_event_loop", false, &config.opts);

let (server_output, ..) = self.str_opt("server_output", "network/server.lua", &config.opts);
let (client_output, ..) = self.str_opt("client_output", "network/client.lua", &config.opts);
Expand Down Expand Up @@ -96,6 +97,7 @@ impl<'src> Converter<'src> {

write_checks,
typescript,
manual_event_loop,

server_output,
client_output,
Expand Down
Loading