blob: e2e7e3e42c866353e88e97bcdd327df73cee3211 [file] [edit]
/*
* Copyright 2026 Rive
*/
#pragma once
#include "rive/scriptnet/http.hpp"
#include "rive/scriptnet/net_policy.hpp"
#include "rive/refcnt.hpp"
#include <cstdint>
// Script networking. A script's fetch() becomes a request here; NetPolicy and
// the per-VM limits run first, then the host's Provider sends it, and the
// outcome is delivered by poll(), which rive_pollAsyncWork() drains each
// frame. Promises therefore settle inside the advance window, never from a
// transport callback.
//
// Outcomes are delivered only on the thread that started the request, so a
// VM living on a worker thread is never touched by another thread's poll.
//
// Not rive::net: that namespace belongs to packages/rive_net (the backend
// client), which hosts like rive-cli link alongside the runtime.
namespace rive::scriptnet
{
using RequestId = uint32_t;
/// Receives the outcome of one request. Exactly one of onResponse, onError or
/// onCancel is called.
class FetchListener : public RefCnt<FetchListener>
{
public:
virtual ~FetchListener() = default;
/// From poll(), on the thread that started the request.
virtual void onResponse(HttpResponse&& response) = 0;
/// From poll(), on the thread that started the request.
virtual void onError(const NetError& error) = 0;
/// From cancel() or cancelAllForOwner(), possibly while the owner's VM is
/// closing, so it must not call into Lua.
virtual void onCancel() {}
};
/// A transport, chosen by the host, never by a file. start() runs on the
/// thread that called fetch(); outcomes come back through complete() and
/// fail() below, from any thread.
class Provider : public RefCnt<Provider>
{
public:
virtual ~Provider() = default;
/// Begin sending `request`, which NetPolicy already checked. The outcome
/// may be reported from inside this call.
virtual void start(RequestId id, const HttpRequest& request) = 0;
/// Best effort; later outcomes for `id` are dropped either way.
virtual void abort(RequestId id) {}
};
/// This platform's transport, or null where there is none yet. Apple builds
/// get a cookie-less, credential-less NSURLSession; Windows builds the same
/// over WinHTTP. Nothing is installed by default: a host opts in with
/// setProvider(makePlatformProvider()).
rcp<Provider> makePlatformProvider();
/// Replaces the transport. Null (the default) denies every request.
void setProvider(rcp<Provider> provider);
bool hasProvider();
void setLimits(const NetLimits& limits);
NetLimits limits();
/// Runs the policy, then hands the request to the provider, or queues it
/// behind `ownerId`'s others while its limits are reached; poll() starts it
/// once they allow. Always returns a nonzero id: refusals settle through
/// poll() like every other outcome, so a script never sees its promise
/// settle during the fetch() call.
RequestId fetch(
uint64_t ownerId,
HttpRequest request,
rcp<FetchListener> listener);
/// Forgets the request and aborts it at the provider. Returns true when the
/// listener got onCancel(), false when it was or is being delivered instead.
bool cancel(RequestId id);
/// cancel() for everything `ownerId` started. Call before the owner's Lua
/// state closes.
void cancelAllForOwner(uint64_t ownerId);
/// Times out overdue requests, starts queued ones this thread made as limits
/// allow, then delivers up to `maxDeliveries` outcomes of requests this
/// thread started. Returns how many it delivered.
uint32_t poll(uint32_t maxDeliveries = 16);
/// True while a request this thread started is outstanding or awaiting
/// delivery.
bool hasPendingWork();
/// hasPendingWork() for the requests `ownerId` started, on any thread.
bool hasPendingWorkForOwner(uint64_t ownerId);
/// Any thread. The provider's outcome for `id`; ignored once `id` has been
/// cancelled, timed out or already completed.
void complete(RequestId id, HttpResponse&& response);
void fail(RequestId id, NetError&& error);
} // namespace rive::scriptnet