Skip to content

Commit

Permalink
feat: add region proxying to actor client (#1540)
Browse files Browse the repository at this point in the history
<!-- Please make sure there is an issue that this PR is correlated to. -->
Fixes RVT-4326
Fixes RVT-4327
## Changes

<!-- If there are frontend changes, please include screenshots. -->
  • Loading branch information
MasterPtato committed Dec 10, 2024
1 parent ac301b5 commit 355c245
Show file tree
Hide file tree
Showing 33 changed files with 1,230 additions and 39 deletions.
6 changes: 6 additions & 0 deletions packages/api/actor/src/route/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,12 @@ define_router! {
},
),
},
"regions" / "resolve": {
GET: regions::resolve(
query: regions::ResolveQuery,
opt_auth: true,
),
},

// MARK: Deprecated
"games" / Uuid / "environments" / Uuid / "servers": {
Expand Down
81 changes: 80 additions & 1 deletion packages/api/actor/src/route/regions.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use api_helper::{anchor::WatchIndexQuery, ctx::Ctx};
use proto::backend;
use rivet_api::models;
use rivet_operation::prelude::*;
use serde::Deserialize;

use crate::{
auth::{Auth, CheckOutput},
Expand All @@ -9,7 +11,7 @@ use crate::{

use super::GlobalQuery;

// MARK: GET /datacenters
// MARK: GET /regions
pub async fn list(
ctx: Ctx<Auth>,
_watch_index: WatchIndexQuery,
Expand Down Expand Up @@ -96,3 +98,80 @@ pub async fn list_deprecated(

Ok(models::ServersListDatacentersResponse { datacenters })
}

// MARK: GET /regions/resolve
#[derive(Debug, Clone, Deserialize)]
pub struct ResolveQuery {
#[serde(flatten)]
global: GlobalQuery,
lat: Option<f64>,
long: Option<f64>,
}

pub async fn resolve(
ctx: Ctx<Auth>,
_watch_index: WatchIndexQuery,
query: ResolveQuery,
) -> GlobalResult<models::ActorResolveRegionResponse> {
let CheckOutput { game_id, .. } = ctx.auth().check(ctx.op_ctx(), &query.global, true).await?;

// Resolve coords
let coords = match (query.lat, query.long) {
(Some(lat), Some(long)) => Some((lat, long)),
(None, None) => ctx.coords(),
_ => bail_with!(API_BAD_QUERY, error = "must have both `lat` and `long`"),
};

let cluster_res = ctx
.op(cluster::ops::get_for_game::Input {
game_ids: vec![game_id],
})
.await?;
let cluster_id = unwrap!(cluster_res.games.first()).cluster_id;

let cluster_dcs_res = ctx
.op(cluster::ops::datacenter::list::Input {
cluster_ids: vec![cluster_id],
})
.await?;
let cluster_dcs = &unwrap!(cluster_dcs_res.clusters.first()).datacenter_ids;

// Get recommended dc
let datacenter_id = if let Some((lat, long)) = coords {
let recommend_res = op!([ctx] region_recommend {
region_ids: cluster_dcs
.iter()
.cloned()
.map(Into::into)
.collect(),
coords: Some(backend::net::Coordinates {
latitude: lat,
longitude: long,
}),
..Default::default()
})
.await?;
let region = unwrap!(recommend_res.regions.first());

unwrap_ref!(region.region_id).as_uuid()
} else {
tracing::warn!("coords not provided to select region");

*unwrap!(cluster_dcs.first())
};

// Fetch dc
let dcs_res = ctx
.op(cluster::ops::datacenter::get::Input {
datacenter_ids: vec![datacenter_id],
})
.await?;
let dc = unwrap!(dcs_res.datacenters.into_iter().next());

Ok(models::ActorResolveRegionResponse {
region: Box::new(models::ActorRegion {
id: dc.name_id,
name: dc.display_name,
}),
})
}
8 changes: 4 additions & 4 deletions sdks/actors/client/deno.jsonc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"imports": {
"@rivet-gg/api": "npm:@rivet-gg/api@0.0.1-rc.2",
"@std/assert": "jsr:@std/assert@^1.0.8"
}
"imports": {
"@rivet-gg/api": "npm:@rivet-gg/api@0.0.1-rc.2",
"@std/assert": "jsr:@std/assert@^1.0.8"
}
}
64 changes: 30 additions & 34 deletions sdks/actors/client/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ActorHandleRaw } from "./handle.ts";
import { ActorTags } from "../../common/src/utils.ts";
import { ActorsRequest } from "../../manager-protocol/src/mod.ts";
import { CreateRequest } from "../../manager-protocol/src/query.ts";
import { RivetClient, RivetClientClient } from "@rivet-gg/api";

export interface WithTagsOpts {
parameters?: unknown;
Expand All @@ -14,36 +15,31 @@ export interface WithTagsOpts {
*
* Private methods (e.g. those starting with `_`) are automatically excluded.
*/
export type ActorHandle<A = unknown> =
& ActorHandleRaw
& {
[
K in keyof A as K extends string ? K extends `_${string}` ? never : K
: K
]: A[K] extends (...args: infer Args) => infer Return
? ActorRPCFunction<Args, Return>
: never;
};
export type ActorHandle<A = unknown> = ActorHandleRaw & {
[K in keyof A as K extends string ? (K extends `_${string}` ? never : K) : K]: A[K] extends (
...args: infer Args
) => infer Return
? ActorRPCFunction<Args, Return>
: never;
};

/**
* RPC function returned by the actor proxy. This will call `ActorHandle.rpc`
* when triggered.
*/
export type ActorRPCFunction<
Args extends Array<unknown> = unknown[],
Response = unknown,
> = (
export type ActorRPCFunction<Args extends Array<unknown> = unknown[], Response = unknown> = (
// Remove the first parameter, since that's `Context<...>`
...args: Args extends [unknown, ...infer Rest] ? Rest : Args
) => Promise<Response>;

export class Client {
private readonly client = new RivetClientClient({ token: TODO });

private region: Promise<RivetClient.actor.Region> | null = this.#fetchRegion();

constructor(private readonly managerEndpoint: string) {}

async withTags<A = unknown>(
tags: ActorTags,
opts?: WithTagsOpts,
): Promise<ActorHandle<A>> {
async withTags<A = unknown>(tags: ActorTags, opts?: WithTagsOpts): Promise<ActorHandle<A>> {
const handle = await this.#createHandle(tags, opts);
return this.#createProxy(handle) as ActorHandle<A>;
}
Expand Down Expand Up @@ -126,16 +122,14 @@ export class Client {
}) as ActorHandle;
}

async #createHandle(
tags: ActorTags,
opts?: WithTagsOpts,
): Promise<ActorHandleRaw> {
async #createHandle(tags: ActorTags, opts?: WithTagsOpts): Promise<ActorHandleRaw> {
const create = opts?.create ?? {
tags,
buildTags: {
name: tags.name,
current: "true",
},
region: (await this.region).id,
};

//client.get("chat_room", { room: "lkjsdf" }, { noCreate: true, parameters: { token: 123 } });
Expand All @@ -152,27 +146,29 @@ export class Client {

const res = await fetch(`${this.managerEndpoint}/actors`, {
method: "POST",
body: JSON.stringify(
{
query: {
getOrCreate: {
tags,
create,
},
body: JSON.stringify({
query: {
getOrCreate: {
tags,
create,
},
} satisfies ActorsRequest,
),
},
} satisfies ActorsRequest),
});

if (!res.ok) {
throw new Error(
`Manager error (${res.statusText}):\n${await res.text()}`,
);
throw new Error(`Manager error (${res.statusText}):\n${await res.text()}`);
}

const resJson: { endpoint: string } = await res.json();
const handle = new ActorHandleRaw(resJson.endpoint, opts?.parameters);
handle.connect();
return handle;
}

async #fetchRegion() {
let { region } = await this.client.actor.regions.resolve({});

return region;
}
}
13 changes: 13 additions & 0 deletions sdks/api/fern/definition/actor/regions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,22 @@ service:
project: optional<string>
environment: optional<string>
response: ListRegionsResponse
resolve:
path: /resolve
method: GET
request:
name: ResolveRegionsRequestQuery
query-parameters:
lat: optional<double>
long: optional<double>
response: ResolveRegionResponse

types:
ListRegionsResponse:
properties:
regions:
type: list<localCommons.Region>
ResolveRegionResponse:
properties:
region:
type: localCommons.Region
34 changes: 34 additions & 0 deletions sdks/api/full/go/actor/regions.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 355c245

Please sign in to comment.