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 polling #136

Closed
wants to merge 17 commits into from
10 changes: 10 additions & 0 deletions docs/config/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ const example = `event MyEvent = {
bar: u8,
},
}`

const pollingExample = `-- Example of using a Zap polling event on the server.
for player, payload in zap.my_event.iter() do
print(player, payload)
end`
</script>

# Events
Expand Down Expand Up @@ -43,6 +48,11 @@ This field determines how the event is listened to on the receiving side.
- `ManySync` events can be listened to by many functions, and they are called synchronously.
- `SingleAsync` events can be listened to by one function, and they are called asynchronously.
- `SingleSync` events can be listened to by one function, and they are called synchronously.
- `Polling` events are received by iterating through the event.

### Polling example

<CodeBlock :code="pollingExample" />

::: danger
Synchronous events are not recommended, and should only be used when performance is critical.
Expand Down
12 changes: 12 additions & 0 deletions docs/config/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ The paths are relative to the configuration file and should point to a lua(u) fi

<CodeBlock :code="outputExample" />

## `call_default`

Determines the default `call` field for events. See [here](events.html#call) for possible options.

### Default

None, all event declarations will need a `call` field.

### Example

<CodeBlock code = 'opt call_default = "ManySync"' />

## `remote_scope`

This option changes the name of the remotes generated by Zap.
Expand Down
1 change: 1 addition & 0 deletions zap/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ pub enum EvCall {
SingleAsync,
ManySync,
ManyAsync,
Polling,
}

#[derive(Debug, Clone)]
Expand Down
4 changes: 4 additions & 0 deletions zap/src/output/luau/base.luau
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

local polling_payload_queues = {}
local polling_queue_cursors = {}
local polling_iterators = {}

local outgoing_buff: buffer
local outgoing_used: number
local outgoing_size: number
Expand Down
177 changes: 130 additions & 47 deletions zap/src/output/luau/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ impl<'src> ClientOutput<'src> {

let fire = self.config.casing.with("Fire", "fire", "fire");
let set_callback = self.config.casing.with("SetCallback", "setCallback", "set_callback");
let iter = self.config.casing.with("Iter", "iter", "iter");
let on = self.config.casing.with("On", "on", "on");
let call = self.config.casing.with("Call", "call", "call");

Expand All @@ -63,11 +64,12 @@ impl<'src> ClientOutput<'src> {
self.indent();

if ev.from == EvSource::Client {
self.push_line(&format!("{fire} = noop"));
self.push_line(&format!("{fire} = noop,"));
} else {
match ev.call {
EvCall::SingleSync | EvCall::SingleAsync => self.push_line(&format!("{set_callback} = noop")),
EvCall::ManySync | EvCall::ManyAsync => self.push_line(&format!("{on} = noop")),
EvCall::SingleSync | EvCall::SingleAsync => self.push_line(&format!("{set_callback} = noop,")),
EvCall::ManySync | EvCall::ManyAsync => self.push_line(&format!("{on} = noop,")),
EvCall::Polling => self.push_line(&format!("{iter} = noop,")),
}
}

Expand Down Expand Up @@ -199,62 +201,73 @@ impl<'src> ClientOutput<'src> {
self.push_stmts(&des::gen(data, "value", true));
}

if ev.call == EvCall::SingleSync || ev.call == EvCall::SingleAsync {
self.push_line(&format!("if events[{id}] then"));
} else {
self.push_line(&format!("if events[{id}][1] then"));
}
match ev.call {
EvCall::Polling => {
let name = ev.name;
self.push_line(&format!(
"table.insert(polling_payload_queues[{id}], if value == nil then \"Zap placeholder value for dataless event \\\"{name}\\\"\" else value)"
));
}
_ => {
if ev.call == EvCall::SingleSync || ev.call == EvCall::SingleAsync {
self.push_line(&format!("if events[{id}] then"));
} else {
self.push_line(&format!("if events[{id}][1] then"));
}

self.indent();
self.indent();

if ev.call == EvCall::ManySync || ev.call == EvCall::ManyAsync {
self.push_line(&format!("for _, cb in events[{id}] do"));
self.indent();
}
if ev.call == EvCall::ManySync || ev.call == EvCall::ManyAsync {
self.push_line(&format!("for _, cb in events[{id}] do"));
self.indent();
}

match ev.call {
EvCall::SingleSync => self.push_line(&format!("events[{id}](value)")),
EvCall::SingleAsync => self.push_line(&format!("task.spawn(events[{id}], value)")),
EvCall::ManySync => self.push_line("cb(value)"),
EvCall::ManyAsync => self.push_line("task.spawn(cb, value)"),
}
match ev.call {
EvCall::SingleSync => self.push_line(&format!("events[{id}](value)")),
EvCall::SingleAsync => self.push_line(&format!("task.spawn(events[{id}], value)")),
EvCall::ManySync => self.push_line("cb(value)"),
EvCall::ManyAsync => self.push_line("task.spawn(cb, value)"),
_ => unreachable!(),
}

if ev.call == EvCall::ManySync || ev.call == EvCall::ManyAsync {
self.dedent();
self.push_line("end");
}
if ev.call == EvCall::ManySync || ev.call == EvCall::ManyAsync {
self.dedent();
self.push_line("end");
}

self.dedent();
self.push_line("else");
self.indent();
self.dedent();
self.push_line("else");
self.indent();

if ev.data.is_some() {
self.push_line(&format!("table.insert(event_queue[{id}], value)"));
self.push_line(&format!("if #event_queue[{id}] > 64 then"));
} else {
self.push_line(&format!("event_queue[{id}] += 1"));
self.push_line(&format!("if event_queue[{id}] > 16 then"));
}
if ev.data.is_some() {
self.push_line(&format!("table.insert(event_queue[{id}], value)"));
self.push_line(&format!("if #event_queue[{id}] > 64 then"));
} else {
self.push_line(&format!("event_queue[{id}] += 1"));
self.push_line(&format!("if event_queue[{id}] > 16 then"));
}

self.indent();
self.push_indent();
self.indent();
self.push_indent();

self.push("warn(`[ZAP] {");
self.push("warn(`[ZAP] {");

if ev.data.is_some() {
self.push("#")
}
if ev.data.is_some() {
self.push("#")
}

self.push(&format!(
"event_queue[{id}]}} events in queue for {}. Did you forget to attach a listener?`)\n",
ev.name
));
self.push(&format!(
"event_queue[{id}]}} events in queue for {}. Did you forget to attach a listener?`)\n",
ev.name
));

self.dedent();
self.push_line("end");
self.dedent();
self.push_line("end");

self.dedent();
self.push_line("end");
self.dedent();
self.push_line("end");
}
}

self.dedent();
}
Expand Down Expand Up @@ -393,6 +406,7 @@ impl<'src> ClientOutput<'src> {
EvCall::SingleAsync => self.push_line(&format!("task.spawn(events[{id}], value)")),
EvCall::ManySync => self.push_line("cb(value)"),
EvCall::ManyAsync => self.push_line("task.spawn(cb, value)"),
_ => (),
}

if ev.call == EvCall::ManySync || ev.call == EvCall::ManyAsync {
Expand Down Expand Up @@ -691,6 +705,24 @@ impl<'src> ClientOutput<'src> {
self.push_line("end,");
}

fn push_explicit_iter(&mut self, ev: &EvDecl) {
let iter = self.config.casing.with("Iter", "iter", "iter");
let id = ev.id;
self.push_indent();
self.push(&format!("{iter} = polling_iterators[{id}] :: () -> "));
if let Some(data) = &ev.data {
self.push("(() -> ");
self.push_ty(&data);
self.push(")");
} else {
let name = ev.name;
self.push(&format!(
"(() -> \"Zap placeholder value for dataless event \"{name}\"."
));
}
self.push(",\n");
}

pub fn push_return_listen(&mut self) {
for ev in self
.config
Expand All @@ -704,6 +736,7 @@ impl<'src> ClientOutput<'src> {
match ev.call {
EvCall::SingleSync | EvCall::SingleAsync => self.push_return_setcallback(ev),
EvCall::ManySync | EvCall::ManyAsync => self.push_return_on(ev),
EvCall::Polling => self.push_explicit_iter(ev),
}

self.dedent();
Expand Down Expand Up @@ -807,6 +840,54 @@ impl<'src> ClientOutput<'src> {
}
}

pub fn push_polling(&mut self) {
let filtered_evdecls = self
.config
.evdecls
.iter()
.filter(|evdecl| evdecl.from == EvSource::Server)
.filter(|evdecl| evdecl.call == EvCall::Polling);

let is_polling_used = filtered_evdecls.clone().next().is_some();
if is_polling_used {
self.push_line("");
}

for evdecl in filtered_evdecls {
let id = evdecl.id;
self.push_line(&format!("polling_payload_queues[{id}] = {{}}"));
self.push_line(&format!("polling_queue_cursors[{id}] = 0"));

self.push_line(&format!("polling_iterators[{id}] = function()"));
self.indent();
self.push_line(&format!("return function()"));
self.indent();
self.push_line(&format!("local payload_queue = polling_payload_queues[{id}]"));
self.push_line(&format!("local cursor = polling_queue_cursors[{id}] + 1"));
self.push_line(&format!("local value = payload_queue[cursor]"));
self.push_line(&format!("if value then"));
self.indent();
self.push_line(&format!("polling_queue_cursors[{id}] = cursor"));
self.push_line("payload_queue[cursor] = nil");
self.push_line("return value");
self.dedent();
self.push_line("else");
self.indent();
self.push_line(&format!("polling_queue_cursors[{id}] = 0"));
self.push_line("return nil");
self.dedent();
self.push_line("end");
self.dedent();
self.push_line("end");
self.dedent();
self.push_line("end");
}
self.push_line("");
self.push_line("table.freeze(polling_payload_queues)");
self.push_line("table.freeze(polling_iterators)");
self.push_line("");
}

pub fn push_return(&mut self) {
self.push_line("local returns = {");
self.indent();
Expand Down Expand Up @@ -896,6 +977,8 @@ impl<'src> ClientOutput<'src> {
self.push_unreliable();
}

self.push_polling();

self.push_return();

self.buf
Expand Down
Loading
Loading