diff --git a/Cargo.lock b/Cargo.lock index ce64edfb..14310302 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4193,6 +4193,7 @@ dependencies = [ "enum_dispatch", "fancy-regex", "flate2", + "fs3", "fs_extra", "futures", "futures-util", diff --git a/core/.gitignore b/core/.gitignore index b617a15b..c4bf5572 100644 --- a/core/.gitignore +++ b/core/.gitignore @@ -9,9 +9,15 @@ target/ # These are backup files generated by rustfmt **/*.rs.bk +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + ## vscode local history .history/ +# jetbrains IDE project files +.idea/ + # Test ground should be local to every developer src/test_ground/ diff --git a/core/Cargo.toml b/core/Cargo.toml index 33cd6eb5..1a815a3a 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -96,6 +96,7 @@ tar = "0.4.38" tempfile = "3.5.0" clap = { version = "4.3.0", features = ["derive"] } once_cell = "1.17.1" +fs3 = "0.5.0" import_map = "0.15.0" [dependencies.uuid] version = "1.1.2" diff --git a/core/bindings/MacroEventKind.ts b/core/bindings/MacroEventKind.ts new file mode 100644 index 00000000..fa3e01ca --- /dev/null +++ b/core/bindings/MacroEventKind.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type MacroEventKind = "Started" | "MainModuleExecuted" | "Stopped"; \ No newline at end of file diff --git a/core/bindings/ProgressionEventID.ts b/core/bindings/ProgressionEventID.ts new file mode 100644 index 00000000..58db8b6b --- /dev/null +++ b/core/bindings/ProgressionEventID.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Snowflake } from "./Snowflake"; + +export type ProgressionEventID = Snowflake; \ No newline at end of file diff --git a/core/bindings/ProgressionStartValue.ts b/core/bindings/ProgressionStartValue.ts index ae0aa55b..e2d68a02 100644 --- a/core/bindings/ProgressionStartValue.ts +++ b/core/bindings/ProgressionStartValue.ts @@ -1,4 +1,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { InstanceUuid } from "./InstanceUuid"; -export type ProgressionStartValue = { type: "InstanceCreation", instance_uuid: InstanceUuid, instance_name: string, port: number, flavour: string, game_type: string, } | { type: "InstanceDelete", instance_uuid: InstanceUuid, }; \ No newline at end of file +export type ProgressionStartValue = { type: "InstanceCreation", instance_uuid: InstanceUuid } | { type: "InstanceDelete", instance_uuid: InstanceUuid, }; \ No newline at end of file diff --git a/core/deno_bindings/ProgressionEventID.ts b/core/deno_bindings/ProgressionEventID.ts new file mode 100644 index 00000000..b556b098 --- /dev/null +++ b/core/deno_bindings/ProgressionEventID.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Snowflake } from "./Snowflake.ts"; + +export type ProgressionEventID = Snowflake; \ No newline at end of file diff --git a/core/deno_bindings/ProgressionStartValue.ts b/core/deno_bindings/ProgressionStartValue.ts index 8d1f2bff..c3c189f6 100644 --- a/core/deno_bindings/ProgressionStartValue.ts +++ b/core/deno_bindings/ProgressionStartValue.ts @@ -1,4 +1,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { InstanceUuid } from "./InstanceUuid.ts"; -export type ProgressionStartValue = { type: "InstanceCreation", instance_uuid: InstanceUuid, instance_name: string, port: number, flavour: string, game_type: string, } | { type: "InstanceDelete", instance_uuid: InstanceUuid, }; +export type ProgressionStartValue = { type: "InstanceCreation", instance_uuid: InstanceUuid } | { type: "InstanceDelete", instance_uuid: InstanceUuid, }; diff --git a/core/src/deno_ops/events/events.ts b/core/src/deno_ops/events/events.ts index f4e3a6fe..a09e6969 100644 --- a/core/src/deno_ops/events/events.ts +++ b/core/src/deno_ops/events/events.ts @@ -3,6 +3,9 @@ import { TaskPID } from "../../../deno_bindings/TaskPID.ts"; import * as InstanceControl from "../instance_control/instance_control.ts" import { InstanceEvent } from "../../../deno_bindings/InstanceEvent.ts"; import { InstanceState } from "../../../deno_bindings/InstanceState.ts"; +import { ProgressionStartValue } from "../../../deno_bindings/ProgressionStartValue.ts"; +import { ProgressionEndValue } from "../../../deno_bindings/ProgressionEndValue.ts"; +import { ProgressionEventID } from "../../../deno_bindings/ProgressionEventID.ts"; // re-exports export type { ClientEvent, TaskPID, InstanceControl, InstanceEvent, InstanceState }; @@ -63,3 +66,14 @@ export function emitStateChange(state: InstanceState, instanceName: string, inst ops.emit_state_change(instanceUuid, instanceName, state); } +export function emitProgressionEventStart(progressionName : string, total : number | null, inner : ProgressionStartValue | null) : ProgressionEventID { + return ops.emit_progression_event_start(progressionName, total, inner); +} + +export function emitProgressiontEventUpdate(eventId : ProgressionEventID, progressMessage : string, progress : number) { + ops.emit_progression_event_update(eventId, progressMessage, progress); +} + +export function emitProgressionEventEnd(eventId : ProgressionEventID, success : boolean, message : string | null, inner : ProgressionEndValue | null) { + ops.emit_progression_event_end(eventId, success, message, inner); +} \ No newline at end of file diff --git a/core/src/deno_ops/events/mod.rs b/core/src/deno_ops/events/mod.rs index 00e1e6e1..a629e51f 100644 --- a/core/src/deno_ops/events/mod.rs +++ b/core/src/deno_ops/events/mod.rs @@ -6,8 +6,11 @@ use deno_core::{ }; use crate::{ - event_broadcaster::{EventBroadcaster, PlayerMessage}, - events::{Event, InstanceEvent}, + event_broadcaster::{EventBroadcaster, PlayerChange, PlayerMessage}, + events::{ + CausedBy, Event, InstanceEvent, ProgressionEndValue, ProgressionEventID, + ProgressionStartValue, + }, macro_executor::MacroPID, traits::t_server::State, types::InstanceUuid, @@ -72,6 +75,17 @@ async fn next_instance_system_message( .await } +#[op] +async fn next_instance_player_change( + state: Rc>, + instance_uuid: InstanceUuid, +) -> PlayerChange { + let event_broadcaster = state.borrow().borrow::().clone(); + event_broadcaster + .next_instance_player_change(&instance_uuid) + .await +} + #[op] fn emit_detach(state: Rc>, macro_pid: MacroPID) { let tx = state.borrow().borrow::().clone(); @@ -108,6 +122,49 @@ fn emit_state_change( )) } +#[op] +fn emit_progression_event_start( + state: Rc>, + progression_name: String, + total: Option, + inner: Option, +) -> ProgressionEventID { + let tx = state.borrow().borrow::().clone(); + let (event, id) = + Event::new_progression_event_start(progression_name, total, inner, CausedBy::System); + tx.send(event); + id +} + +#[op] +fn emit_progression_event_update( + state: Rc>, + event_id: ProgressionEventID, + progress_msg: String, + progress: f64, +) { + let tx = state.borrow().borrow::().clone(); + tx.send(Event::new_progression_event_update( + &event_id, + progress_msg, + progress, + )); +} + +#[op] +fn emit_progression_event_end( + state: Rc>, + event_id: ProgressionEventID, + success: bool, + message: Option, + inner: Option, +) { + let tx = state.borrow().borrow::().clone(); + tx.send(Event::new_progression_event_end( + event_id, success, message, inner, + )); +} + pub fn register_all_event_ops( worker_options: &mut deno_runtime::worker::WorkerOptions, event_broadcaster: EventBroadcaster, @@ -124,6 +181,10 @@ pub fn register_all_event_ops( next_instance_output::decl(), next_instance_player_message::decl(), next_instance_system_message::decl(), + next_instance_player_change::decl(), + emit_progression_event_start::decl(), + emit_progression_event_update::decl(), + emit_progression_event_end::decl(), ]) .state(|state| { state.put(event_broadcaster); diff --git a/core/src/event_broadcaster.rs b/core/src/event_broadcaster.rs index cf3d8b0d..727c25f0 100644 --- a/core/src/event_broadcaster.rs +++ b/core/src/event_broadcaster.rs @@ -1,9 +1,11 @@ +use std::collections::HashSet; + use tokio::sync::broadcast::{Receiver, Sender}; use tracing::error; use crate::{ events::{Event, EventInner, InstanceEvent, InstanceEventInner}, - traits::t_server::State, + traits::{t_player::Player, t_server::State}, types::InstanceUuid, }; @@ -18,6 +20,13 @@ pub struct PlayerMessage { pub message: String, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PlayerChange { + player_list: HashSet, + players_joined: HashSet, + players_left: HashSet, +} + impl EventBroadcaster { pub fn new(capacity: usize) -> (Self, Receiver) { let (event_tx, rx) = tokio::sync::broadcast::channel(capacity); @@ -105,6 +114,23 @@ impl EventBroadcaster { } } } + pub async fn next_instance_player_change(&self, instance_uuid: &InstanceUuid) -> PlayerChange { + loop { + let event = self.next_instance_event(instance_uuid).await; + if let InstanceEventInner::PlayerChange { + player_list, + players_joined, + players_left, + } = event.instance_event_inner + { + return PlayerChange { + player_list, + players_joined, + players_left, + }; + } + } + } } impl From for Sender { diff --git a/core/src/events.rs b/core/src/events.rs index 5dfd338e..4ea0f634 100644 --- a/core/src/events.rs +++ b/core/src/events.rs @@ -219,10 +219,6 @@ pub enum ProgressionEndValue { pub enum ProgressionStartValue { InstanceCreation { instance_uuid: InstanceUuid, - instance_name: String, - port: u32, - flavour: String, - game_type: String, }, InstanceDelete { instance_uuid: InstanceUuid, @@ -284,6 +280,9 @@ pub fn new_fs_event(operation: FSOperation, target: FSTarget, caused_by: CausedB } } +#[derive(Serialize, Deserialize, TS)] +#[serde(transparent)] +#[ts(export)] pub struct ProgressionEventID(Snowflake); #[derive(Serialize, Deserialize, Clone, Debug, TS, PartialEq)] diff --git a/core/src/handlers/instance.rs b/core/src/handlers/instance.rs index 84355983..76d60b16 100644 --- a/core/src/handlers/instance.rs +++ b/core/src/handlers/instance.rs @@ -109,8 +109,6 @@ pub async fn create_minecraft_instance( let uuid = instance_uuid.clone(); let instance_name = setup_config.name.clone(); let event_broadcaster = state.event_broadcaster.clone(); - let port = setup_config.port; - let flavour = setup_config.flavour.clone(); let caused_by = CausedBy::User { user_id: requester.uid.clone(), user_name: requester.username.clone(), @@ -121,10 +119,6 @@ pub async fn create_minecraft_instance( Some(10.0), Some(ProgressionStartValue::InstanceCreation { instance_uuid: uuid.clone(), - instance_name: instance_name.clone(), - port, - flavour: flavour.to_string(), - game_type: "minecraft".to_string(), }), caused_by, ); diff --git a/core/src/implementations/minecraft/server.rs b/core/src/implementations/minecraft/server.rs index 3ca6e567..fb9de761 100644 --- a/core/src/implementations/minecraft/server.rs +++ b/core/src/implementations/minecraft/server.rs @@ -419,7 +419,7 @@ impl TServer for MinecraftInstance { } else if let Some(PlayerMessage { player, message }) = parse_player_msg(&line) { - let _ = event_broadcaster.send(Event { + event_broadcaster.send(Event { event_inner: EventInner::InstanceEvent(InstanceEvent { instance_uuid: uuid.clone(), instance_event_inner: diff --git a/core/src/lib.rs b/core/src/lib.rs index 4827e364..c676184c 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -68,6 +68,8 @@ use tracing_subscriber::{prelude::__tracing_subscriber_SubscriberExt, EnvFilter} use traits::{t_configurable::TConfigurable, t_server::MonitorReport, t_server::TServer}; use types::{DotLodestoneConfig, InstanceUuid}; use uuid::Uuid; +use fs3::FileExt; + pub mod auth; pub mod db; mod deno_ops; @@ -409,6 +411,16 @@ pub async fn run( check_for_core_update().await; output_sys_info(); + let lockfile_path = lodestone_path.join("lodestone.lock"); + let file = if lockfile_path.as_path().exists() { + std::fs::File::open(lockfile_path.as_path()).expect("failed to open lockfile") + } else { + std::fs::File::create(lockfile_path.as_path()).expect("failed to create lockfile") + }; + if file.try_lock_exclusive().is_err() { + panic!("Another instance of lodestone might be running"); + } + let _ = migrate(&lodestone_path).map_err(|e| { error!("Error while migrating lodestone: {}. Lodestone will still start, but one or more instance may be in an erroneous state", e); }); @@ -644,6 +656,8 @@ pub async fn run( .unwrap(); } }); + // capture file into the move block + let _file = file; select! { _ = write_to_db_task => info!("Write to db task exited"), _ = event_buffer_task => info!("Event buffer task exited"), @@ -710,4 +724,4 @@ pub async fn run( guard, shutdown_tx, ) -} +} \ No newline at end of file