Make GM commands able to be executed at runtime properly, networking and eventgraph code adjustment

This commit is contained in:
xeon 2025-05-31 17:22:44 +03:00
parent 8b03b7c099
commit 6ff7dd3ff0
23 changed files with 561 additions and 277 deletions

View file

@ -1,3 +1,4 @@
pub mod config_util; pub mod config_util;
pub mod logging; pub mod logging;
pub mod ref_util;
pub mod time_util; pub mod time_util;

View file

@ -0,0 +1,29 @@
use std::ops::Deref;
pub enum Ref<T: 'static> {
Static(&'static T),
Owned(T),
}
impl<T> Deref for Ref<T> {
type Target = T;
fn deref(&self) -> &T {
match self {
Self::Static(s) => s,
Self::Owned(s) => s,
}
}
}
impl<T> From<&'static T> for Ref<T> {
fn from(value: &'static T) -> Self {
Self::Static(value)
}
}
impl<T> From<T> for Ref<T> {
fn from(value: T) -> Self {
Self::Owned(value)
}
}

View file

@ -17,6 +17,7 @@ pub enum SectionEvent {
OnEnter, OnEnter,
OnInteract, OnInteract,
OnStart, OnStart,
GM,
#[serde(untagged)] #[serde(untagged)]
Custom(String), Custom(String),
} }

View file

@ -42,6 +42,9 @@ pub enum GMCmd {
SetControlGuiseAvatar { SetControlGuiseAvatar {
avatar_id: u32, avatar_id: u32,
}, },
UnlockHollowQuest {
quest_id: u32,
},
Jump { Jump {
section_id: u32, section_id: u32,
transform_id: String, transform_id: String,

View file

@ -5,12 +5,13 @@ mod uid;
use std::collections::HashMap; use std::collections::HashMap;
pub use action::ActionBase; pub use action::ActionBase;
use common::ref_util::Ref;
use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_enum::{IntoPrimitive, TryFromPrimitive};
use tracing::error; use tracing::error;
pub use uid::EventUID; pub use uid::EventUID;
use config::{ConfigEvent, ConfigEventAction, GraphReference, SectionEvent}; use config::{ConfigEvent, ConfigEventAction, GraphReference, SectionEvent};
use vivian_proto::ActionInfo; use vivian_proto::{ActionInfo, server_only::GraphReferenceType};
use crate::listener::LogicEventListener; use crate::listener::LogicEventListener;
@ -28,26 +29,39 @@ pub enum EventState {
pub struct Event { pub struct Event {
pub ty: SectionEvent, pub ty: SectionEvent,
pub graph: GraphReference,
pub tag: u32, pub tag: u32,
pub config: &'static ConfigEvent, pub graph_id: GraphID,
pub config: Ref<ConfigEvent>,
pub cur_action_index: isize, pub cur_action_index: isize,
pub state: EventState, pub state: EventState,
pub specials: HashMap<String, i32>, pub specials: HashMap<String, i32>,
} }
pub struct GraphID(pub u32, pub GraphReferenceType);
impl From<GraphReference> for GraphID {
fn from(value: GraphReference) -> Self {
match value {
GraphReference::MainCitySection(id) => Self(id, GraphReferenceType::MainCitySection),
GraphReference::Interact(id) => Self(id, GraphReferenceType::Interact),
GraphReference::Quest(id) => Self(id, GraphReferenceType::Quest),
GraphReference::HollowEvent(id) => Self(id, GraphReferenceType::HollowEvent),
}
}
}
impl Event { impl Event {
pub fn new( pub fn new(
ty: SectionEvent, ty: SectionEvent,
tag: u32, tag: u32,
graph: GraphReference, graph_id: impl Into<GraphID>,
config: &'static ConfigEvent, config: impl Into<Ref<ConfigEvent>>,
) -> Self { ) -> Self {
Self { Self {
ty, ty,
graph, graph_id: graph_id.into(),
tag, tag,
config, config: config.into(),
cur_action_index: -1, cur_action_index: -1,
state: EventState::Initing, state: EventState::Initing,
specials: HashMap::new(), // TODO: take initial ones from graph config specials: HashMap::new(), // TODO: take initial ones from graph config
@ -87,7 +101,7 @@ impl Event {
{ {
self.cur_action_index = index as isize; self.cur_action_index = index as isize;
let uid = ((self.graph.id() as u64) << 32) | own_uid.event_id() as u64; let uid = ((self.graph_id.0 as u64) << 32) | own_uid.event_id() as u64;
if action_listener.should_execute_action(uid, action, logic_listener, &self.specials) { if action_listener.should_execute_action(uid, action, logic_listener, &self.specials) {
action_listener.execute_action(uid, action, logic_listener, &self.specials); action_listener.execute_action(uid, action, logic_listener, &self.specials);
if let Some(client_action_info) = action::action_to_proto(action) { if let Some(client_action_info) = action::action_to_proto(action) {
@ -106,7 +120,7 @@ impl Event {
} }
pub fn is_persistent(&self) -> bool { pub fn is_persistent(&self) -> bool {
self.ty != SectionEvent::OnInteract !matches!(self.ty, SectionEvent::OnInteract | SectionEvent::GM)
} }
pub fn is_finished(&self) -> bool { pub fn is_finished(&self) -> bool {

View file

@ -7,12 +7,12 @@ use npc::{Interact, InteractTarget, SceneUnit};
use tracing::{error, warn}; use tracing::{error, warn};
use vivian_proto::{ use vivian_proto::{
EnterSceneScNotify, EventGraphOwnerType, FinishEventGraphScNotify, SectionEventScNotify, EnterSceneScNotify, EventGraphOwnerType, FinishEventGraphScNotify, SectionEventScNotify,
common::TimePeriodType, common::TimePeriodType, server_only::GraphReferenceType,
}; };
use crate::{ use crate::{
LogicResources, LogicResources,
event::{ActionListener, Event, EventState, EventUID, event_util}, event::{ActionListener, Event, EventState, EventUID, GraphID, event_util},
listener::{LogicEventListener, NotifyListener}, listener::{LogicEventListener, NotifyListener},
math::{Scale, Transform}, math::{Scale, Transform},
scene::SceneType, scene::SceneType,
@ -365,6 +365,21 @@ impl GameHallState {
self.refresh_required = false; self.refresh_required = false;
} }
pub fn execute_gm_event(&mut self, config: ConfigEvent, listener: &mut dyn LogicEventListener) {
let event_uid = EventUID::new(EventGraphOwnerType::Scene, config.id);
let mut event = Event::new(
SectionEvent::GM,
0,
GraphID(0, GraphReferenceType::None),
config,
);
event.wakeup(event_uid, self, listener);
if !event.is_finished() {
self.running_events.insert(event_uid, event);
}
}
fn initiate_event( fn initiate_event(
&mut self, &mut self,
graph: GraphReference, graph: GraphReference,

View file

@ -6,10 +6,9 @@ use std::{
use super::*; use super::*;
use config::{GraphReference, SectionEvent}; use config::{GraphReference, SectionEvent};
use property::{PrimitiveProperty, Property, PropertyHashMap}; use property::{PrimitiveProperty, Property, PropertyHashMap};
use tracing::warn;
use vivian_logic::{ use vivian_logic::{
dungeon::{Dungeon, DungeonEquipment}, dungeon::{Dungeon, DungeonEquipment},
event::{EventState, EventUID}, event::{EventState, EventUID, GraphID},
hall::npc::InteractTarget, hall::npc::InteractTarget,
math::Scale, math::Scale,
scene::ELocalPlayType, scene::ELocalPlayType,
@ -54,7 +53,8 @@ pub struct HallSectionSnapshot {
} }
pub struct EventSnapshot { pub struct EventSnapshot {
pub graph: GraphReference, pub graph_id: u32,
pub graph_type: GraphReferenceType,
pub ty: SectionEvent, pub ty: SectionEvent,
pub uid: EventUID, pub uid: EventUID,
pub tag: u32, pub tag: u32,
@ -371,18 +371,29 @@ impl HallSceneSnapshot {
.attached_graph_list .attached_graph_list
.into_iter() .into_iter()
.filter_map(|info| { .filter_map(|info| {
load_graph_reference(info.reference_type(), info.reference_id) Some(match info.reference_type() {
GraphReferenceType::Interact => {
GraphReference::Interact(info.reference_id)
}
GraphReferenceType::HollowEvent => {
GraphReference::HollowEvent(info.reference_id)
}
GraphReferenceType::Quest => {
GraphReference::Quest(info.reference_id)
}
GraphReferenceType::MainCitySection => {
GraphReference::MainCitySection(info.reference_id)
}
GraphReferenceType::None => return None,
})
}) })
.collect(), .collect(),
event_snapshots: section event_snapshots: section
.event_state_list .event_state_list
.into_iter() .into_iter()
.map(|info| EventSnapshot { .map(|info| EventSnapshot {
graph: load_graph_reference( graph_id: info.graph_reference_id,
info.graph_reference_type(), graph_type: info.graph_reference_type(),
info.graph_reference_id,
)
.unwrap(),
ty: SectionEvent::from_str(&info.name).unwrap(), ty: SectionEvent::from_str(&info.name).unwrap(),
uid: info.event_uid.into(), uid: info.event_uid.into(),
state: EventState::try_from(info.event_state).unwrap(), state: EventState::try_from(info.event_state).unwrap(),
@ -446,7 +457,7 @@ impl HallSceneSnapshot {
.attached_graphs .attached_graphs
.iter() .iter()
.map(|graph_ref| { .map(|graph_ref| {
let (ty, id) = save_graph_reference(graph_ref); let GraphID(id, ty) = GraphID::from(*graph_ref);
AttachedGraphInfo { AttachedGraphInfo {
reference_id: id, reference_id: id,
reference_type: ty.into(), reference_type: ty.into(),
@ -456,18 +467,14 @@ impl HallSceneSnapshot {
event_state_list: section event_state_list: section
.event_snapshots .event_snapshots
.iter() .iter()
.map(|event| { .map(|event| EventStateInfo {
let (ty, id) = save_graph_reference(&event.graph); graph_reference_id: event.graph_id,
graph_reference_type: event.graph_type.into(),
EventStateInfo { name: event.ty.to_string(),
graph_reference_id: id, event_uid: *event.uid,
graph_reference_type: ty.into(), event_state: event.state.into(),
name: event.ty.to_string(), cur_action_idx: event.cur_action_idx as i32,
event_uid: *event.uid, tag: event.tag,
event_state: event.state.into(),
cur_action_idx: event.cur_action_idx as i32,
tag: event.tag,
}
}) })
.collect(), .collect(),
already_executed_event_uid_list: section already_executed_event_uid_list: section
@ -524,24 +531,14 @@ impl LongFightSceneSnapshot {
} }
} }
fn load_graph_reference(ty: GraphReferenceType, id: u32) -> Option<GraphReference> { impl EventSnapshot {
match ty { pub fn graph_reference(&self) -> Option<GraphReference> {
GraphReferenceType::MainCitySection => Some(GraphReference::MainCitySection(id)), Some(match self.graph_type {
GraphReferenceType::Interact => Some(GraphReference::Interact(id)), GraphReferenceType::Interact => GraphReference::Interact(self.graph_id),
GraphReferenceType::Quest => Some(GraphReference::Quest(id)), GraphReferenceType::HollowEvent => GraphReference::HollowEvent(self.graph_id),
GraphReferenceType::HollowEvent => Some(GraphReference::HollowEvent(id)), GraphReferenceType::Quest => GraphReference::Quest(self.graph_id),
invalid => { GraphReferenceType::MainCitySection => GraphReference::MainCitySection(self.graph_id),
warn!("invalid graph reference type in snapshot: {invalid:?}"); GraphReferenceType::None => return None,
None })
}
}
}
fn save_graph_reference(graph_ref: &GraphReference) -> (GraphReferenceType, u32) {
match graph_ref {
GraphReference::MainCitySection(id) => (GraphReferenceType::MainCitySection, *id),
GraphReference::Interact(id) => (GraphReferenceType::Interact, *id),
GraphReference::Quest(id) => (GraphReferenceType::Quest, *id),
GraphReference::HollowEvent(id) => (GraphReferenceType::HollowEvent, *id),
} }
} }

View file

@ -7,6 +7,8 @@ pub struct PacketHead {
pub player_uid: u32, pub player_uid: u32,
#[prost(uint64, tag = "3")] #[prost(uint64, tag = "3")]
pub session_id: u64, pub session_id: u64,
#[prost(uint64, tag = "4")]
pub gate_session_id: u64,
#[prost(uint32, tag = "11")] #[prost(uint32, tag = "11")]
pub ack_packet_id: u32, pub ack_packet_id: u32,
} }

View file

@ -770,4 +770,11 @@ pub struct GmTalkByMuipRsp {
pub retcode: i32, pub retcode: i32,
#[prost(string, tag = "2")] #[prost(string, tag = "2")]
pub retmsg: ::prost::alloc::string::String, pub retmsg: ::prost::alloc::string::String,
}
#[derive(::proto_derive::NetCmd)]
#[cmd_id(10010)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ClientPerformNotify {
#[prost(message, repeated, tag = "1")]
pub notify_list: ::prost::alloc::vec::Vec<NetCommand>,
} }

View file

@ -155,6 +155,7 @@ impl NetworkClient {
stream, stream,
Arc::clone(self.listener.get().unwrap()), Arc::clone(self.listener.get().unwrap()),
None, None,
None,
)); ));
let _ = self let _ = self

View file

@ -1,4 +1,7 @@
use std::sync::{Arc, OnceLock}; use std::{
net::SocketAddr,
sync::{Arc, OnceLock},
};
use tokio::{ use tokio::{
io::{AsyncReadExt, AsyncWriteExt}, io::{AsyncReadExt, AsyncWriteExt},
@ -17,6 +20,7 @@ use super::{
}; };
pub struct NetworkEntity { pub struct NetworkEntity {
pub local_addr: Option<SocketAddr>,
sender: mpsc::UnboundedSender<NetPacket>, sender: mpsc::UnboundedSender<NetPacket>,
encryption_state: Arc<EncryptionState>, encryption_state: Arc<EncryptionState>,
cancellation: CancellationToken, cancellation: CancellationToken,
@ -47,6 +51,7 @@ impl NetworkEntity {
id: u64, id: u64,
stream: TcpStream, stream: TcpStream,
listener: Arc<dyn NetworkEventListener>, listener: Arc<dyn NetworkEventListener>,
local_addr: Option<SocketAddr>,
xorpad: Option<&'static [u8; 4096]>, xorpad: Option<&'static [u8; 4096]>,
) -> Self { ) -> Self {
let (tx, rx) = mpsc::unbounded_channel(); let (tx, rx) = mpsc::unbounded_channel();
@ -70,6 +75,7 @@ impl NetworkEntity {
tokio::spawn(Self::send_loop(w, rx)); tokio::spawn(Self::send_loop(w, rx));
Self { Self {
local_addr,
sender: tx, sender: tx,
encryption_state, encryption_state,
cancellation, cancellation,

View file

@ -26,33 +26,38 @@ pub trait NetworkEventListener: Send + Sync + 'static {
} }
pub struct NetworkServer { pub struct NetworkServer {
bind_addr: SocketAddr, bind_addresses: Vec<SocketAddr>,
} }
pub struct NetworkEntityManager { pub struct NetworkEntityManager {
entity_id_counter: AtomicU64, entity_id_counter: AtomicU64,
entity_map: HashMap<u64, Arc<NetworkEntity>>, entity_map: HashMap<u64, Arc<NetworkEntity>>,
event_listener: Arc<dyn NetworkEventListener>, event_listener: Arc<dyn NetworkEventListener>,
xorpad: Option<&'static [u8; 4096]>, xorpad_map: std::collections::HashMap<SocketAddr, &'static [u8; 4096]>,
} }
impl NetworkEntityManager { impl NetworkEntityManager {
pub fn new( pub fn new(
event_listener: impl NetworkEventListener, event_listener: impl NetworkEventListener,
xorpad: Option<&'static [u8; 4096]>, xorpad_map: std::collections::HashMap<SocketAddr, &'static [u8; 4096]>,
) -> Self { ) -> Self {
Self { Self {
entity_id_counter: AtomicU64::new(1), entity_id_counter: AtomicU64::new(1),
entity_map: HashMap::new(), entity_map: HashMap::new(),
event_listener: Arc::new(event_listener), event_listener: Arc::new(event_listener),
xorpad, xorpad_map,
} }
} }
pub(crate) fn on_connect(&self, stream: TcpStream) { pub(crate) fn on_connect(&self, stream: TcpStream, local_addr: SocketAddr) {
let id = self.entity_id_counter.fetch_add(1, Ordering::SeqCst); let id = self.entity_id_counter.fetch_add(1, Ordering::SeqCst);
let entity = let entity = NetworkEntity::start(
NetworkEntity::start(id, stream, Arc::clone(&self.event_listener), self.xorpad); id,
stream,
Arc::clone(&self.event_listener),
Some(local_addr),
self.xorpad_map.get(&local_addr).copied(),
);
let _ = self.entity_map.insert(id, Arc::new(entity)); let _ = self.entity_map.insert(id, Arc::new(entity));
} }
@ -67,20 +72,28 @@ impl NetworkEntityManager {
} }
impl NetworkServer { impl NetworkServer {
async fn accept_loop(service: Arc<ServiceContext>, listener: TcpListener) { async fn accept_loop(
service: Arc<ServiceContext>,
local_addr: SocketAddr,
listener: TcpListener,
) {
loop { loop {
if let Ok((stream, _addr)) = listener.accept().await { if let Ok((stream, _addr)) = listener.accept().await {
service.resolve::<NetworkEntityManager>().on_connect(stream); service
.resolve::<NetworkEntityManager>()
.on_connect(stream, local_addr);
} }
} }
} }
} }
impl ConfigurableServiceModule for NetworkServer { impl ConfigurableServiceModule for NetworkServer {
type Config = SocketAddr; type Config = Vec<SocketAddr>;
fn new(_context: &crate::ServiceContext, config: Self::Config) -> Self { fn new(_context: &crate::ServiceContext, config: Self::Config) -> Self {
Self { bind_addr: config } Self {
bind_addresses: config,
}
} }
} }
@ -92,12 +105,15 @@ impl Startable for NetworkEntityManager {
impl ServiceModule for NetworkServer { impl ServiceModule for NetworkServer {
fn run(self: Arc<Self>, service: Arc<crate::ServiceContext>) -> Result<(), Box<dyn Error>> { fn run(self: Arc<Self>, service: Arc<crate::ServiceContext>) -> Result<(), Box<dyn Error>> {
tokio::spawn(Self::accept_loop( for &bind_addr in self.bind_addresses.iter() {
service, tokio::spawn(Self::accept_loop(
net_util::tcp_bind_sync(self.bind_addr)?, Arc::clone(&service),
)); bind_addr,
net_util::tcp_bind_sync(bind_addr)?,
));
debug!("server is listening at {}", self.bind_addr); debug!("server is listening at {bind_addr}");
}
Ok(()) Ok(())
} }

View file

@ -120,10 +120,10 @@ async fn handle_player_get_token(
let entity = scope.fetch::<Arc<NetworkEntity>>().unwrap(); let entity = scope.fetch::<Arc<NetworkEntity>>().unwrap();
entity.send(NetPacket::new( entity.send(NetPacket::new(
PacketHead { PacketHead {
packet_id: 0,
session_id: head.session_id, session_id: head.session_id,
player_uid: head.player_uid, player_uid: head.player_uid,
ack_packet_id: head.packet_id, ack_packet_id: head.packet_id,
..Default::default()
}, },
response, response,
)); ));
@ -145,10 +145,10 @@ async fn handle_player_get_data(scope: &ServiceScope, head: PacketHead, request:
entity.send(NetPacket::new( entity.send(NetPacket::new(
PacketHead { PacketHead {
packet_id: 0,
player_uid: head.player_uid, player_uid: head.player_uid,
session_id: head.session_id, session_id: head.session_id,
ack_packet_id: head.packet_id, ack_packet_id: head.packet_id,
..Default::default()
}, },
PlayerGetDataRsp { PlayerGetDataRsp {
retcode: 0, retcode: 0,

View file

@ -1,3 +1,5 @@
use std::collections::HashMap;
use config::ServerConfig; use config::ServerConfig;
use const_format::concatcp; use const_format::concatcp;
use database::DbConnection; use database::DbConnection;
@ -40,9 +42,9 @@ async fn main() -> Result<(), StartupError> {
let service = ServiceContext::new() let service = ServiceContext::new()
.insert_module(db_connection) .insert_module(db_connection)
.insert_module(NetworkEntityManager::new(listener, None)) .insert_module(NetworkEntityManager::new(listener, HashMap::new()))
.configure_module::<TokenVerificationModule>(config.auth) .configure_module::<TokenVerificationModule>(config.auth)
.configure_module::<NetworkServer>(env.services.get(&SERVICE_TYPE).unwrap().addr) .configure_module::<NetworkServer>(vec![env.services.get(&SERVICE_TYPE).unwrap().addr])
.start()?; .start()?;
let _ = service_tx.send(service); let _ = service_tx.send(service);

View file

@ -0,0 +1,158 @@
use std::collections::HashMap;
use tokio::sync::mpsc;
use vivian_logic::GameState;
use crate::{
handlers::{NetContext, NotifyQueue},
player::{ModelManager, Player},
resources::NapResources,
util,
};
use super::{ClusterCommand, ClusterPerformanceReport, PlayerCommandResult, PlayerUpdate};
#[derive(Default)]
struct ClusterState {
cluster_id: u32,
slots: HashMap<u32, PlayerSlot>,
report_tx: Option<mpsc::Sender<ClusterPerformanceReport>>,
player_update_tx: Option<tokio::sync::mpsc::Sender<PlayerUpdate>>,
}
struct PlayerSlot {
player: Player,
game_state: Option<GameState>,
}
pub fn logic_loop(
cluster_id: u32,
resources: &'static NapResources,
command_rx: std::sync::mpsc::Receiver<ClusterCommand>,
) {
let mut state = ClusterState {
cluster_id,
..Default::default()
};
while let Ok(command) = command_rx.recv() {
match command {
ClusterCommand::SetReportListener(tx) => {
state.report_tx = Some(tx);
}
ClusterCommand::SetPlayerUpdateListener(tx) => {
state.player_update_tx = Some(tx);
}
ClusterCommand::CreateSlot {
player_uid,
player_data,
} => {
let mut player = Player::load_from_pb(player_uid, *player_data, resources);
player.on_login();
state.slots.insert(
player_uid,
PlayerSlot {
player,
game_state: None,
},
);
send_performance_report(&state);
}
ClusterCommand::RemovePlayer { player_uid } => {
if let Some(mut slot) = state.slots.remove(&player_uid) {
if let Some(player_update_tx) = state.player_update_tx.as_ref() {
if let Some(mut state) = slot.game_state {
slot.player.save_scene_snapshot(&mut state);
let data = slot.player.build_full_update();
let _ = player_update_tx.blocking_send(PlayerUpdate {
uid: slot.player.uid,
data,
});
}
}
send_performance_report(&state);
}
}
ClusterCommand::PushPlayerCommand {
player_uid,
cmd_id,
body,
result_awaiter_tx,
} => {
if let Some(slot) = state.slots.get_mut(&player_uid) {
let mut context =
NetContext::new(&mut slot.player, &mut slot.game_state, resources);
crate::handlers::handle_command(&mut context, cmd_id, body);
enqueue_player_notifies(
context.player,
context.game_state.as_mut(),
&mut context.notifies,
);
let _ = result_awaiter_tx.send(PlayerCommandResult {
notifies: context.notifies.drain(),
response: context.response,
});
if let Some(player_update_tx) = state.player_update_tx.as_ref() {
if slot.player.is_any_model_modified() {
let data = slot.player.build_partial_update();
let _ = player_update_tx.blocking_send(PlayerUpdate {
uid: slot.player.uid,
data,
});
slot.player.changes_acknowledged();
}
}
}
}
ClusterCommand::PushGmCommand {
player_uid,
cmd,
result_awaiter_tx,
} => {
if let Some(slot) = state.slots.get_mut(&player_uid) {
util::gm_util::execute_gm_cmd(&mut slot.player, slot.game_state.as_mut(), cmd);
let mut queue = NotifyQueue::default();
enqueue_player_notifies(&slot.player, slot.game_state.as_mut(), &mut queue);
let _ = result_awaiter_tx.send(queue);
}
}
}
}
}
fn enqueue_player_notifies(
player: &Player,
state: Option<&mut GameState>,
queue: &mut NotifyQueue,
) {
if player.loading_finished() && player.has_models_to_synchronize() {
queue.prepend_notify(player.build_player_sync_notify());
// TODO: more generic way to send notifies from models?
player.avatar_model.send_add_avatar_notify(queue);
}
if let Some(state) = state {
state.flush_notifies(queue);
}
}
fn send_performance_report(state: &ClusterState) {
state.report_tx.as_ref().inspect(|tx| {
let _ = tx.blocking_send(ClusterPerformanceReport {
cluster_id: state.cluster_id,
player_slots: state.slots.len(),
});
});
}

View file

@ -8,9 +8,9 @@ use std::{
thread, thread,
}; };
use tokio::sync::oneshot; use tokio::{sync::oneshot, task::JoinSet};
use tracing::info; use tracing::info;
use vivian_logic::{GameState, debug::GMCmd}; use vivian_logic::debug::GMCmd;
use vivian_proto::{ use vivian_proto::{
head::PacketHead, head::PacketHead,
server_only::{PlayerData, PlayerDataChangedNotify}, server_only::{PlayerData, PlayerDataChangedNotify},
@ -20,19 +20,32 @@ use vivian_service::{
network::client::NetworkClient, network::client::NetworkClient,
}; };
use crate::{ use crate::{config::ClusterConfig, handlers::NotifyQueue, resources::NapResources};
config::ClusterConfig,
handlers::NetContext, mod logic_loop;
player::{ModelManager, Player},
resources::NapResources,
util::gm_util,
};
pub struct PlayerCommandResult { pub struct PlayerCommandResult {
pub notifies: Vec<(u16, Vec<u8>)>, pub notifies: Vec<(u16, Vec<u8>)>,
pub response: Option<(u16, Vec<u8>)>, pub response: Option<(u16, Vec<u8>)>,
} }
#[derive(Clone)]
pub struct PlayerLogicCluster {
#[expect(dead_code)]
pub id: u32,
command_tx: mpsc::Sender<ClusterCommand>,
}
pub struct PlayerLogicClusterManager {
cluster_map: HashMap<u32, PlayerLogicCluster>,
cluster_load_map: Arc<HashMap<u32, AtomicUsize>>,
}
pub struct LogicClusterConfig {
pub cluster: ClusterConfig,
pub resources: &'static NapResources,
}
enum ClusterCommand { enum ClusterCommand {
SetReportListener(tokio::sync::mpsc::Sender<ClusterPerformanceReport>), SetReportListener(tokio::sync::mpsc::Sender<ClusterPerformanceReport>),
SetPlayerUpdateListener(tokio::sync::mpsc::Sender<PlayerUpdate>), SetPlayerUpdateListener(tokio::sync::mpsc::Sender<PlayerUpdate>),
@ -49,6 +62,7 @@ enum ClusterCommand {
PushGmCommand { PushGmCommand {
player_uid: u32, player_uid: u32,
cmd: GMCmd, cmd: GMCmd,
result_awaiter_tx: oneshot::Sender<NotifyQueue>,
}, },
RemovePlayer { RemovePlayer {
player_uid: u32, player_uid: u32,
@ -66,28 +80,6 @@ struct PlayerUpdate {
data: PlayerData, data: PlayerData,
} }
#[derive(Clone)]
pub struct PlayerLogicCluster {
#[expect(dead_code)]
pub id: u32,
command_tx: mpsc::Sender<ClusterCommand>,
}
pub struct PlayerSlot {
pub player: Player,
pub game_state: Option<GameState>,
}
pub struct PlayerLogicClusterManager {
cluster_map: HashMap<u32, PlayerLogicCluster>,
cluster_load_map: Arc<HashMap<u32, AtomicUsize>>,
}
pub struct LogicClusterConfig {
pub cluster: ClusterConfig,
pub resources: &'static NapResources,
}
impl Startable for PlayerLogicClusterManager { impl Startable for PlayerLogicClusterManager {
fn start( fn start(
&self, &self,
@ -102,9 +94,16 @@ impl Startable for PlayerLogicClusterManager {
cluster.set_player_update_listener(player_update_tx.clone()); cluster.set_player_update_listener(player_update_tx.clone());
}); });
tokio::spawn(Self::player_update_receiver_loop(player_update_rx, service)); let mut join_set = JoinSet::new();
join_set.spawn(Self::player_update_receiver_loop(player_update_rx, service));
join_set.spawn(Self::report_receiver_loop(
report_rx,
Arc::clone(&self.cluster_load_map),
));
Self::report_receiver_loop(report_rx, Arc::clone(&self.cluster_load_map)) async {
let _ = join_set.join_all().await;
}
} }
} }
@ -180,7 +179,7 @@ impl PlayerLogicClusterManager {
impl PlayerLogicCluster { impl PlayerLogicCluster {
pub fn spawn(id: u32, resources: &'static NapResources) -> Self { pub fn spawn(id: u32, resources: &'static NapResources) -> Self {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
thread::spawn(move || Self::logic_loop(id, resources, rx)); thread::spawn(move || logic_loop::logic_loop(id, resources, rx));
Self { id, command_tx: tx } Self { id, command_tx: tx }
} }
@ -220,10 +219,14 @@ impl PlayerLogicCluster {
rx.await.unwrap() rx.await.unwrap()
} }
pub fn push_gm_command(&self, player_uid: u32, cmd: GMCmd) { pub async fn push_gm_command(&self, player_uid: u32, cmd: GMCmd) -> NotifyQueue {
let (tx, rx) = oneshot::channel();
self.command_tx self.command_tx
.send(ClusterCommand::PushGmCommand { player_uid, cmd }) .send(ClusterCommand::PushGmCommand { player_uid, cmd, result_awaiter_tx: tx })
.unwrap(); .unwrap();
rx.await.unwrap()
} }
fn set_report_listener(&self, tx: tokio::sync::mpsc::Sender<ClusterPerformanceReport>) { fn set_report_listener(&self, tx: tokio::sync::mpsc::Sender<ClusterPerformanceReport>) {
@ -237,123 +240,4 @@ impl PlayerLogicCluster {
.send(ClusterCommand::SetPlayerUpdateListener(tx)) .send(ClusterCommand::SetPlayerUpdateListener(tx))
.unwrap(); .unwrap();
} }
fn logic_loop(
cluster_id: u32,
resources: &'static NapResources,
command_rx: mpsc::Receiver<ClusterCommand>,
) {
let mut slots = HashMap::new();
let mut report_tx = None;
let mut player_update_tx = None;
while let Ok(command) = command_rx.recv() {
match command {
ClusterCommand::SetReportListener(tx) => {
report_tx = Some(tx);
}
ClusterCommand::SetPlayerUpdateListener(tx) => {
player_update_tx = Some(tx);
}
ClusterCommand::CreateSlot {
player_uid,
player_data,
} => {
let mut player = Player::load_from_pb(player_uid, *player_data, resources);
player.on_login();
slots.insert(
player_uid,
PlayerSlot {
player,
game_state: None,
},
);
report_tx.as_ref().inspect(|tx| {
let _ = tx.blocking_send(ClusterPerformanceReport {
cluster_id,
player_slots: slots.len(),
});
});
}
ClusterCommand::RemovePlayer { player_uid } => {
if let Some(mut slot) = slots.remove(&player_uid) {
if let Some(player_update_tx) = player_update_tx.as_ref() {
if let Some(mut state) = slot.game_state {
slot.player.save_scene_snapshot(&mut state);
let data = slot.player.build_full_update();
let _ = player_update_tx.blocking_send(PlayerUpdate {
uid: slot.player.uid,
data,
});
}
}
report_tx.as_ref().inspect(|tx| {
let _ = tx.blocking_send(ClusterPerformanceReport {
cluster_id,
player_slots: slots.len(),
});
});
}
}
ClusterCommand::PushPlayerCommand {
player_uid,
cmd_id,
body,
result_awaiter_tx,
} => {
if let Some(slot) = slots.get_mut(&player_uid) {
let mut context =
NetContext::new(&mut slot.player, &mut slot.game_state, resources);
super::handlers::handle_command(&mut context, cmd_id, body);
if context.player.loading_finished()
&& context.player.has_models_to_synchronize()
{
context
.notifies
.prepend_notify(context.player.build_player_sync_notify());
// TODO: more generic way to send notifies from models?
context
.player
.avatar_model
.send_add_avatar_notify(&mut context.notifies);
}
if let Some(state) = context.game_state.as_mut() {
state.flush_notifies(&mut context.notifies);
}
let _ = result_awaiter_tx.send(PlayerCommandResult {
notifies: context.notifies.drain(),
response: context.response,
});
if let Some(player_update_tx) = player_update_tx.as_ref() {
if slot.player.is_any_model_modified() {
let data = slot.player.build_partial_update();
let _ = player_update_tx.blocking_send(PlayerUpdate {
uid: slot.player.uid,
data,
});
slot.player.changes_acknowledged();
}
}
}
}
ClusterCommand::PushGmCommand { player_uid, cmd } => {
if let Some(slot) = slots.get_mut(&player_uid) {
gm_util::execute_gm_cmd(&mut slot.player, cmd);
}
}
}
}
}
} }

View file

@ -1,4 +1,4 @@
use std::sync::OnceLock; use std::{collections::HashMap, sync::OnceLock};
use cluster::{LogicClusterConfig, PlayerLogicClusterManager}; use cluster::{LogicClusterConfig, PlayerLogicClusterManager};
use common::logging::init_tracing; use common::logging::init_tracing;
@ -57,8 +57,8 @@ async fn main() -> Result<(), StartupError> {
let (service_tx, listener) = session::start_handler_task(); let (service_tx, listener) = session::start_handler_task();
let service = ServiceContext::new() let service = ServiceContext::new()
.insert_module(NetworkEntityManager::new(listener, None)) .insert_module(NetworkEntityManager::new(listener, HashMap::new()))
.configure_module::<NetworkServer>(env_cfg.services.get(&SERVICE_TYPE).unwrap().addr) .configure_module::<NetworkServer>(vec![env_cfg.services.get(&SERVICE_TYPE).unwrap().addr])
.configure_module::<NetworkClient>(env_cfg.services) .configure_module::<NetworkClient>(env_cfg.services)
.configure_module::<PlayerLogicClusterManager>(LogicClusterConfig { .configure_module::<PlayerLogicClusterManager>(LogicClusterConfig {
cluster: config.cluster, cluster: config.cluster,

View file

@ -161,7 +161,7 @@ impl Player {
gm_group gm_group
.commands .commands
.iter() .iter()
.for_each(|cmd| gm_util::execute_gm_cmd(self, cmd.clone())); .for_each(|cmd| gm_util::execute_gm_cmd(self, None, cmd.clone()));
}); });
} }
@ -402,23 +402,25 @@ impl Player {
section_snapshot.already_executed_events.clone(); section_snapshot.already_executed_events.clone();
for snapshot in section_snapshot.event_snapshots.iter() { for snapshot in section_snapshot.event_snapshots.iter() {
if let Some(config) = self if let Some(reference) = snapshot.graph_reference() {
.resources if let Some(config) = self
.event_graphs .resources
.get(snapshot.graph, section_snapshot.section_id) .event_graphs
{ .get(reference, section_snapshot.section_id)
if let Some(event) = config.events.get(&snapshot.ty) { {
let mut event = Event::new( if let Some(event) = config.events.get(&snapshot.ty) {
snapshot.ty.clone(), let mut event = Event::new(
snapshot.tag, snapshot.ty.clone(),
snapshot.graph, snapshot.tag,
event, reference,
); event,
);
event.state = snapshot.state; event.state = snapshot.state;
event.cur_action_index = snapshot.cur_action_idx; event.cur_action_index = snapshot.cur_action_idx;
state.running_events.insert(snapshot.uid, event); state.running_events.insert(snapshot.uid, event);
}
} }
} }
} }
@ -594,7 +596,8 @@ impl Player {
.iter() .iter()
.filter(|(_, event)| event.is_persistent()) .filter(|(_, event)| event.is_persistent())
.map(|(&uid, event)| EventSnapshot { .map(|(&uid, event)| EventSnapshot {
graph: event.graph, graph_id: event.graph_id.0,
graph_type: event.graph_id.1,
ty: event.ty.clone(), ty: event.ty.clone(),
uid, uid,
state: event.state, state: event.state,

View file

@ -11,8 +11,9 @@ use vivian_proto::{
PlayerLoginCsReq, PlayerLoginScRsp, PlayerLoginCsReq, PlayerLoginScRsp,
head::PacketHead, head::PacketHead,
server_only::{ server_only::{
ExecuteClientCommandReq, ExecuteClientCommandRsp, GmTalkByMuipReq, GmTalkByMuipRsp, ClientPerformNotify, ExecuteClientCommandReq, ExecuteClientCommandRsp, GmTalkByMuipReq,
NetCommand, PlayerGetDataReq, PlayerGetDataRsp, StopPlayerLogicReq, StopPlayerLogicRsp, GmTalkByMuipRsp, NetCommand, PlayerGetDataReq, PlayerGetDataRsp, StopPlayerLogicReq,
StopPlayerLogicRsp,
}, },
}; };
use vivian_service::{ use vivian_service::{
@ -28,6 +29,7 @@ use vivian_service::{
pub struct PlayerSession { pub struct PlayerSession {
pub player_uid: u32, pub player_uid: u32,
pub gate_session_id: u64,
pub cluster: PlayerLogicCluster, pub cluster: PlayerLogicCluster,
} }
@ -86,6 +88,7 @@ async fn handler_loop(
async fn process_cmd(service: Arc<ServiceContext>, entity_id: u64, packet: NetPacket) { async fn process_cmd(service: Arc<ServiceContext>, entity_id: u64, packet: NetPacket) {
if let Some(entity) = service.resolve::<NetworkEntityManager>().get(entity_id) { if let Some(entity) = service.resolve::<NetworkEntityManager>().get(entity_id) {
let scope = service.new_scope().with_variable(entity).build(); let scope = service.new_scope().with_variable(entity).build();
if let Err(err) = handle_cmd(scope.as_ref(), packet).await { if let Err(err) = handle_cmd(scope.as_ref(), packet).await {
error!("failed to decode client cmd: {err}"); error!("failed to decode client cmd: {err}");
} }
@ -150,6 +153,7 @@ async fn handle_player_login_cs_req(
head.player_uid, head.player_uid,
Arc::new(PlayerSession { Arc::new(PlayerSession {
player_uid: head.player_uid, player_uid: head.player_uid,
gate_session_id: head.gate_session_id,
cluster, cluster,
}), }),
); );
@ -235,7 +239,32 @@ async fn handle_gm_talk_by_muip_req(
} }
}; };
session.cluster.push_gm_command(session.player_uid, cmd); let mut notifies = session
.cluster
.push_gm_command(session.player_uid, cmd)
.await;
scope
.resolve::<NetworkClient>()
.send_notify(
ServiceType::Gate,
PacketHead {
gate_session_id: session.gate_session_id,
player_uid: session.player_uid,
..Default::default()
},
ClientPerformNotify {
notify_list: notifies
.drain()
.into_iter()
.map(|(cmd_id, body)| NetCommand {
cmd_id: cmd_id as u32,
body,
})
.collect(),
},
)
.await;
GmTalkByMuipRsp { GmTalkByMuipRsp {
retcode: 0, retcode: 0,
@ -263,10 +292,10 @@ macro_rules! handlers {
let entity = scope.fetch::<Arc<NetworkEntity>>().unwrap(); let entity = scope.fetch::<Arc<NetworkEntity>>().unwrap();
entity.send(NetPacket::new( entity.send(NetPacket::new(
PacketHead { PacketHead {
packet_id: 0,
player_uid: packet.head.player_uid, player_uid: packet.head.player_uid,
session_id: packet.head.session_id, session_id: packet.head.session_id,
ack_packet_id: packet.head.packet_id, ack_packet_id: packet.head.packet_id,
..Default::default()
}, },
response, response,
)); ));

View file

@ -1,8 +1,10 @@
use std::cmp; use std::cmp;
use config::{ActionSwitchSection, ConfigEvent, ConfigEventAction};
use itertools::Itertools; use itertools::Itertools;
use tracing::{error, instrument}; use tracing::{error, instrument};
use vivian_logic::{ use vivian_logic::{
GameState,
debug::GMCmd, debug::GMCmd,
item::{EAvatarSkillType, EquipItem}, item::{EAvatarSkillType, EquipItem},
}; };
@ -10,11 +12,11 @@ use vivian_models::SceneSnapshotExt;
use crate::{ use crate::{
player::{AddItemSource, Player}, player::{AddItemSource, Player},
util::{avatar_util, item_util}, util::{avatar_util, item_util, quest_util},
}; };
#[instrument(skip(player))] #[instrument(skip(player, state))]
pub fn execute_gm_cmd(player: &mut Player, cmd: GMCmd) { pub fn execute_gm_cmd(player: &mut Player, state: Option<&mut GameState>, cmd: GMCmd) {
use GMCmd::*; use GMCmd::*;
match cmd { match cmd {
@ -164,6 +166,21 @@ pub fn execute_gm_cmd(player: &mut Player, cmd: GMCmd) {
} }
SetControlGuiseAvatar { avatar_id } => { SetControlGuiseAvatar { avatar_id } => {
player.basic_model.control_guise_avatar_id.set(avatar_id); player.basic_model.control_guise_avatar_id.set(avatar_id);
if let Some(GameState::Hall(hall)) = state {
hall.control_guise_avatar_id = avatar_id;
hall.force_refresh();
}
}
UnlockHollowQuest { quest_id } => {
if player
.resources
.templates
.hollow_quest_template_tb()
.any(|q| q.id() == quest_id)
{
quest_util::add_hollow_quest(player, quest_id);
}
} }
Jump { Jump {
section_id, section_id,
@ -177,6 +194,25 @@ pub fn execute_gm_cmd(player: &mut Player, cmd: GMCmd) {
if let SceneSnapshotExt::Hall(hall) = &mut default_scene.ext { if let SceneSnapshotExt::Hall(hall) = &mut default_scene.ext {
hall.cur_section_id = section_id; hall.cur_section_id = section_id;
player.main_city_model.transform_id.set(&transform_id); player.main_city_model.transform_id.set(&transform_id);
if let Some(GameState::Hall(hall)) = state {
hall.execute_gm_event(
ConfigEvent {
id: 1337,
actions: vec![ConfigEventAction::ActionSwitchSection(
ActionSwitchSection {
id: 100,
section_id,
transform: transform_id,
camera_x: 6000,
camera_y: 0,
predicates: Vec::new(),
},
)],
},
player,
);
}
} }
} }
} }

View file

@ -1,4 +1,4 @@
use std::sync::Arc; use std::{net::SocketAddr, sync::Arc};
use tokio::sync::{mpsc, oneshot}; use tokio::sync::{mpsc, oneshot};
use tracing::{debug, error, warn}; use tracing::{debug, error, warn};
@ -6,7 +6,7 @@ use vivian_proto::{
KeepAliveNotify, NetCmd, PlayerGetTokenCsReq, PlayerGetTokenScRsp, PlayerLoginCsReq, KeepAliveNotify, NetCmd, PlayerGetTokenCsReq, PlayerGetTokenScRsp, PlayerLoginCsReq,
PlayerLoginScRsp, PlayerLogoutCsReq, PlayerLoginScRsp, PlayerLogoutCsReq,
head::PacketHead, head::PacketHead,
server_only::{StopPlayerLogicReq, StopPlayerLogicRsp}, server_only::{ClientPerformNotify, StopPlayerLogicReq, StopPlayerLogicRsp},
}; };
use vivian_service::{ use vivian_service::{
ServiceContext, ServiceScope, ServiceContext, ServiceScope,
@ -28,6 +28,8 @@ enum NetworkEvent {
Disconnect(u64), Disconnect(u64),
} }
struct InternalAddr(SocketAddr);
impl NetworkEventListener for NetworkListener { impl NetworkEventListener for NetworkListener {
fn on_receive(&self, entity_id: u64, packet: NetPacket) { fn on_receive(&self, entity_id: u64, packet: NetPacket) {
self.0 self.0
@ -41,19 +43,22 @@ impl NetworkEventListener for NetworkListener {
} }
} }
pub fn start_handler_task() -> ( pub fn start_handler_task(
internal_addr: SocketAddr,
) -> (
oneshot::Sender<Arc<ServiceContext>>, oneshot::Sender<Arc<ServiceContext>>,
impl NetworkEventListener, impl NetworkEventListener,
) { ) {
let (sv_tx, sv_rx) = oneshot::channel(); let (sv_tx, sv_rx) = oneshot::channel();
let (tx, rx) = mpsc::unbounded_channel(); let (tx, rx) = mpsc::unbounded_channel();
tokio::spawn(handler_loop(sv_rx, rx)); tokio::spawn(handler_loop(sv_rx, internal_addr, rx));
(sv_tx, NetworkListener(tx)) (sv_tx, NetworkListener(tx))
} }
async fn handler_loop( async fn handler_loop(
lazy_service: oneshot::Receiver<Arc<ServiceContext>>, lazy_service: oneshot::Receiver<Arc<ServiceContext>>,
internal_addr: SocketAddr,
mut rx: mpsc::UnboundedReceiver<NetworkEvent>, mut rx: mpsc::UnboundedReceiver<NetworkEvent>,
) { ) {
let service = lazy_service.await.unwrap(); let service = lazy_service.await.unwrap();
@ -61,7 +66,7 @@ async fn handler_loop(
while let Some(event) = rx.recv().await { while let Some(event) = rx.recv().await {
match event { match event {
NetworkEvent::Receive(id, packet) => { NetworkEvent::Receive(id, packet) => {
tokio::spawn(process_cmd(Arc::clone(&service), id, packet)); tokio::spawn(process_cmd(Arc::clone(&service), id, internal_addr, packet));
} }
NetworkEvent::Disconnect(id) => { NetworkEvent::Disconnect(id) => {
tokio::spawn(process_disconnect(Arc::clone(&service), id)); tokio::spawn(process_disconnect(Arc::clone(&service), id));
@ -70,10 +75,20 @@ async fn handler_loop(
} }
} }
async fn process_cmd(service: Arc<ServiceContext>, entity_id: u64, mut packet: NetPacket) { async fn process_cmd(
service: Arc<ServiceContext>,
entity_id: u64,
internal_addr: SocketAddr,
mut packet: NetPacket,
) {
if let Some(entity) = service.resolve::<NetworkEntityManager>().get(entity_id) { if let Some(entity) = service.resolve::<NetworkEntityManager>().get(entity_id) {
packet.head.session_id = entity_id; packet.head.session_id = entity_id;
let scope = service.new_scope().with_variable(entity).build(); let scope = service
.new_scope()
.with_variable(InternalAddr(internal_addr))
.with_variable(entity)
.build();
if let Err(err) = handle_cmd(scope.as_ref(), packet).await { if let Err(err) = handle_cmd(scope.as_ref(), packet).await {
error!("failed to decode client cmd: {err}"); error!("failed to decode client cmd: {err}");
} }
@ -93,10 +108,9 @@ async fn process_disconnect(service: Arc<ServiceContext>, entity_id: u64) {
.send_request::<_, StopPlayerLogicRsp>( .send_request::<_, StopPlayerLogicRsp>(
ServiceType::Game, ServiceType::Game,
PacketHead { PacketHead {
packet_id: 0,
player_uid: session.uid(), player_uid: session.uid(),
session_id: entity_id, session_id: entity_id,
ack_packet_id: 0, ..Default::default()
}, },
StopPlayerLogicReq { StopPlayerLogicReq {
player_uid: session.uid(), player_uid: session.uid(),
@ -133,6 +147,9 @@ async fn handle_cmd(scope: &ServiceScope, packet: NetPacket) -> Result<(), GetPr
handle_player_logout(scope, packet.head, packet.get_proto()?).await handle_player_logout(scope, packet.head, packet.get_proto()?).await
} }
KeepAliveNotify::CMD_ID => (), KeepAliveNotify::CMD_ID => (),
ClientPerformNotify::CMD_ID => {
handle_client_perform(scope, packet.head, packet.get_proto()?).await
}
cmd_id if cmd_id < 10000 => { cmd_id if cmd_id < 10000 => {
if let Some(session) = scope if let Some(session) = scope
.resolve::<PlayerSessionManager>() .resolve::<PlayerSessionManager>()
@ -249,6 +266,7 @@ async fn handle_player_login(scope: &ServiceScope, head: PacketHead, request: Pl
PacketHead { PacketHead {
player_uid: session.uid(), player_uid: session.uid(),
session_id: head.session_id, session_id: head.session_id,
gate_session_id: head.session_id,
..Default::default() ..Default::default()
}, },
request, request,
@ -275,6 +293,7 @@ async fn handle_player_login(scope: &ServiceScope, head: PacketHead, request: Pl
player_uid: session.uid(), player_uid: session.uid(),
session_id: head.session_id, session_id: head.session_id,
ack_packet_id: head.packet_id, ack_packet_id: head.packet_id,
..Default::default()
}, },
rsp, rsp,
)); ));
@ -290,10 +309,9 @@ async fn handle_player_logout(scope: &ServiceScope, head: PacketHead, _request:
.send_request::<_, StopPlayerLogicRsp>( .send_request::<_, StopPlayerLogicRsp>(
ServiceType::Game, ServiceType::Game,
PacketHead { PacketHead {
packet_id: 0,
player_uid: session.uid(), player_uid: session.uid(),
session_id: head.session_id, gate_session_id: head.session_id,
ack_packet_id: 0, ..Default::default()
}, },
StopPlayerLogicReq { StopPlayerLogicReq {
player_uid: session.uid(), player_uid: session.uid(),
@ -305,3 +323,33 @@ async fn handle_player_logout(scope: &ServiceScope, head: PacketHead, _request:
} }
} }
} }
async fn handle_client_perform(
scope: &ServiceScope,
head: PacketHead,
notify: ClientPerformNotify,
) {
let InternalAddr(internal_addr) = scope.fetch().unwrap();
let entity = scope.fetch::<Arc<NetworkEntity>>().unwrap();
if Some(internal_addr) != entity.local_addr.as_ref() {
warn!("received server-only packet from client!");
return;
}
if let Some(session) = scope.resolve::<PlayerSessionManager>().get(head.gate_session_id) {
debug!("pushing notifies: {:?}", notify.notify_list);
session
.push_notifies(
notify
.notify_list
.into_iter()
.map(|notify| (notify.cmd_id as u16, notify.body))
.collect(),
)
.await;
} else {
debug!("no session with id {}", head.session_id);
}
}

View file

@ -1,4 +1,4 @@
use std::sync::LazyLock; use std::{collections::HashMap, sync::LazyLock};
use config::ServerConfig; use config::ServerConfig;
use const_format::concatcp; use const_format::concatcp;
@ -6,7 +6,7 @@ use encryption::SecurityModule;
use session::PlayerSessionManager; use session::PlayerSessionManager;
use vivian_service::{ use vivian_service::{
ServiceContext, ServiceError, ServiceContext, ServiceError,
config::load_environment_config, config::{ServiceType, load_environment_config},
network::{NetworkEntityManager, NetworkServer, client::NetworkClient}, network::{NetworkEntityManager, NetworkServer, client::NetworkClient},
}; };
@ -15,6 +15,7 @@ mod encryption;
mod handlers; mod handlers;
mod session; mod session;
const SERVICE_TYPE: ServiceType = ServiceType::Gate;
const CONFIG_DIR: &str = "config/20-gate-server/"; const CONFIG_DIR: &str = "config/20-gate-server/";
#[tokio::main] #[tokio::main]
@ -27,18 +28,19 @@ async fn main() -> Result<(), ServiceError> {
}); });
let env_cfg = load_environment_config(); let env_cfg = load_environment_config();
let internal_addr = env_cfg.services.get(&SERVICE_TYPE).unwrap().addr;
common::logging::init_tracing(tracing::Level::DEBUG); common::logging::init_tracing(tracing::Level::DEBUG);
let (service_tx, listener) = handlers::start_handler_task(); let (service_tx, listener) = handlers::start_handler_task(internal_addr);
let service = ServiceContext::new() let service = ServiceContext::new()
.insert_module(NetworkEntityManager::new( .insert_module(NetworkEntityManager::new(
listener, listener,
Some(&CONFIG.client_secret_key.xorpad), HashMap::from([(CONFIG.bind_addr, &CONFIG.client_secret_key.xorpad)]),
)) ))
.with_module::<PlayerSessionManager>() .with_module::<PlayerSessionManager>()
.configure_module::<SecurityModule>(&CONFIG.rsa_versions) .configure_module::<SecurityModule>(&CONFIG.rsa_versions)
.configure_module::<NetworkServer>(CONFIG.bind_addr) .configure_module::<NetworkServer>(vec![CONFIG.bind_addr, internal_addr])
.configure_module::<NetworkClient>(env_cfg.services) .configure_module::<NetworkClient>(env_cfg.services)
.start()?; .start()?;

View file

@ -24,7 +24,12 @@ pub struct PlayerSession {
login_failed_status: OnceLock<i32>, login_failed_status: OnceLock<i32>,
is_logged_in: OnceLock<()>, is_logged_in: OnceLock<()>,
is_logged_out: OnceLock<()>, is_logged_out: OnceLock<()>,
packet_tx: OnceLock<mpsc::Sender<NetPacket>>, packet_tx: OnceLock<mpsc::Sender<QueueItem>>,
}
enum QueueItem {
FromClient(NetPacket),
FromServer(Vec<(u16, Vec<u8>)>),
} }
impl PlayerSessionManager { impl PlayerSessionManager {
@ -59,12 +64,18 @@ impl PlayerSession {
pub async fn enqueue(&self, packet: NetPacket) { pub async fn enqueue(&self, packet: NetPacket) {
if let Some(tx) = self.packet_tx.get() { if let Some(tx) = self.packet_tx.get() {
let _ = tx.send(packet).await; let _ = tx.send(QueueItem::FromClient(packet)).await;
}
}
pub async fn push_notifies(&self, notifies: Vec<(u16, Vec<u8>)>) {
if let Some(tx) = self.packet_tx.get() {
let _ = tx.send(QueueItem::FromServer(notifies)).await;
} }
} }
async fn packet_handler_loop( async fn packet_handler_loop(
mut rx: mpsc::Receiver<NetPacket>, mut rx: mpsc::Receiver<QueueItem>,
uid: u32, uid: u32,
ctx: Arc<ServiceContext>, ctx: Arc<ServiceContext>,
network_entity: Arc<NetworkEntity>, network_entity: Arc<NetworkEntity>,
@ -77,6 +88,26 @@ impl PlayerSession {
let mut buffered_requests = HashMap::with_capacity(BUFFERED_REQUESTS_LIMIT); let mut buffered_requests = HashMap::with_capacity(BUFFERED_REQUESTS_LIMIT);
while let Some(packet) = rx.recv().await { while let Some(packet) = rx.recv().await {
let packet = match packet {
QueueItem::FromClient(packet) => packet,
QueueItem::FromServer(packets) => {
packets.into_iter().for_each(|(cmd_id, body)| {
last_server_packet_id += 1;
network_entity.send(NetPacket {
cmd_id,
body,
head: PacketHead {
packet_id: last_server_packet_id,
player_uid: uid,
..Default::default()
},
});
});
continue;
}
};
let packet_id = packet.head.packet_id; let packet_id = packet.head.packet_id;
if packet_id != 0 && packet_id != (last_client_packet_id + 1) { if packet_id != 0 && packet_id != (last_client_packet_id + 1) {
@ -130,10 +161,9 @@ impl PlayerSession {
.send_request::<_, ExecuteClientCommandRsp>( .send_request::<_, ExecuteClientCommandRsp>(
ServiceType::Game, ServiceType::Game,
PacketHead { PacketHead {
packet_id: 0,
player_uid: uid, player_uid: uid,
session_id: packet.head.session_id, session_id: packet.head.session_id,
ack_packet_id: 0, ..Default::default()
}, },
ExecuteClientCommandReq { ExecuteClientCommandReq {
command: Some(NetCommand { command: Some(NetCommand {