use futures::StreamExt; mod rbx_util; mod message_handler; mod nats_types; mod types; mod download; mod check; mod check_mapfix; mod check_submission; mod create; mod create_mapfix; mod create_submission; mod upload_mapfix; mod upload_submission; mod validator; mod validate_mapfix; mod validate_submission; #[allow(dead_code)] #[derive(Debug)] pub enum StartupError{ API(submissions_api::ReqwestError), NatsConnect(async_nats::ConnectError), NatsGetStream(async_nats::jetstream::context::GetStreamError), NatsConsumer(async_nats::jetstream::stream::ConsumerError), NatsConsumerUpdate(async_nats::jetstream::stream::ConsumerUpdateError), NatsStream(async_nats::jetstream::consumer::StreamError), } impl std::fmt::Display for StartupError{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ write!(f,"{self:?}") } } impl std::error::Error for StartupError{} pub const PARALLEL_REQUESTS:usize=16; #[tokio::main] async fn main()->Result<(),StartupError>{ let group_id:Option<u64>=match std::env::var("ROBLOX_GROUP_ID"){ Ok(s)=>match s.as_str(){ "None"=>None, _=>Some(s.parse().expect("ROBLOX_GROUP_ID int parse")), }, Err(e)=>Err(e).expect("ROBLOX_GROUP_ID env required"), }; // create / upload models through STRAFESNET_CI2 account let cookie=std::env::var("RBXCOOKIE").expect("RBXCOOKIE env required"); let cookie_context=rbx_asset::cookie::Context::new(rbx_asset::cookie::Cookie::new(cookie)); // download models through cloud api let api_key=std::env::var("RBX_API_KEY").expect("RBX_API_KEY env required"); let cloud_context=rbx_asset::cloud::Context::new(rbx_asset::cloud::ApiKey::new(api_key)); // maps-service api let api_host_internal=std::env::var("API_HOST_INTERNAL").expect("API_HOST_INTERNAL env required"); let api=submissions_api::internal::Context::new(api_host_internal).map_err(StartupError::API)?; // nats let nats_host=std::env::var("NATS_HOST").expect("NATS_HOST env required"); let nats_fut=async{ const STREAM_NAME:&str="maptest"; const DURABLE_NAME:&str="validation"; const FILTER_SUBJECT:&str="maptest.>"; let nats_config=async_nats::jetstream::consumer::pull::Config{ name:Some(DURABLE_NAME.to_owned()), durable_name:Some(DURABLE_NAME.to_owned()), filter_subject:FILTER_SUBJECT.to_owned(), ..Default::default() }; let nasty=async_nats::connect(nats_host).await.map_err(StartupError::NatsConnect)?; // use nats jetstream let stream=async_nats::jetstream::new(nasty) .get_stream(STREAM_NAME).await.map_err(StartupError::NatsGetStream)?; let consumer=stream.get_or_create_consumer(DURABLE_NAME,nats_config.clone()).await.map_err(StartupError::NatsConsumer)?; // check if config matches expected config if consumer.cached_info().config.filter_subject!=FILTER_SUBJECT{ stream.update_consumer(nats_config).await.map_err(StartupError::NatsConsumerUpdate)?; } // only need messages consumer.messages().await.map_err(StartupError::NatsStream) }; let message_handler=message_handler::MessageHandler::new(cloud_context,cookie_context,group_id,api); // Create a signal listener for SIGTERM let mut sig_term=tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()).expect("Failed to create SIGTERM signal listener"); // run futures let mut messages=nats_fut.await?; // process up to PARALLEL_REQUESTS in parallel let main_loop=async move{ static SEM:tokio::sync::Semaphore=tokio::sync::Semaphore::const_new(PARALLEL_REQUESTS); // use memory leak to make static lifetime let message_handler=Box::leak(Box::new(message_handler)); // acquire a permit before attempting to receive a message, exit if either fails while let (Ok(permit),Some(message_result))=(SEM.acquire().await,messages.next().await){ // handle the message on a new thread (mainly to decode the model file) tokio::spawn(async{ match message_handler.handle_message_result(message_result).await{ Ok(())=>println!("[Validation] Success, hooray!"), Err(e)=>println!("[Validation] There was an error, oopsie! {e}"), } // explicitly call drop to make the move semantics and permit release more obvious drop(permit); }); } }; // race sigkill and main loop termination and then die tokio::select!{ _=sig_term.recv()=>(), _=main_loop=>(), }; Ok(()) }