Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow prototype references to be actual references #196

Merged
merged 1 commit into from
Jun 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions server-config/src/common.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,54 @@
use std::borrow::Cow;

use serde::{Deserialize, Serialize};

use crate::{PlanePrototype, PrototypeRef, PtrRef, StringRef, ValidationError};

/// Common fields that are just copied directly between [`GamePrototype`] and
/// [`GameConfig`].
///
/// [`GamePrototype`]: crate::GamePrototype
/// [`GameConfig`]: crate::GameConfig
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct GameConfigCommon {
#[serde(bound(
serialize = "Ref::PlaneRef: Serialize",
deserialize = "Ref::PlaneRef: Deserialize<'de>"
))]
pub struct GameConfigCommon<'a, Ref: PrototypeRef<'a>> {
/// The default plane that a player joining the game will get unless the
/// server overrides it.
pub default_plane: Cow<'static, str>,
pub default_plane: Ref::PlaneRef,
}

impl GameConfigCommon {
impl GameConfigCommon<'_, StringRef> {
pub const fn new() -> Self {
Self {
default_plane: Cow::Borrowed("predator"),
}
}

pub(crate) fn resolve<'a>(
self,
planes: &'a [PlanePrototype<'a, PtrRef>],
) -> Result<GameConfigCommon<'a, PtrRef>, ValidationError> {
let default_plane =
planes
.iter()
.find(|p| p.name == self.default_plane)
.ok_or(ValidationError::custom(
"default_plane",
format_args!(
"default_plane refers to a plane prototype `{}` which does not exist",
self.default_plane
),
))?;

Ok(GameConfigCommon { default_plane })
}
}

impl Default for GameConfigCommon {
impl Default for GameConfigCommon<'_, StringRef> {
fn default() -> Self {
Self::new()
}
Expand Down
233 changes: 128 additions & 105 deletions server-config/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,148 +1,114 @@
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt;
use std::mem::ManuallyDrop;
use std::ops::{Deref, DerefMut};
use std::ptr::NonNull;

use crate::{
GameConfigCommon, GamePrototype, MissilePrototype, PlanePrototype, SpecialPrototype,
SpecialPrototypeData, ValidationError, ValidationExt,
GameConfigCommon, GamePrototype, MissilePrototype, PlanePrototype, PtrRef, SpecialPrototype,
StringRef, ValidationError, ValidationExt,
};

#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct GameConfig {
pub planes: HashMap<String, PlanePrototype>,
pub missiles: HashMap<String, MissilePrototype>,
pub specials: HashMap<String, SpecialPrototype>,
pub planes: HashMap<&'static str, &'static PlanePrototype<'static, PtrRef>>,
pub missiles: HashMap<&'static str, &'static MissilePrototype>,
pub specials: HashMap<&'static str, &'static SpecialPrototype<'static, PtrRef>>,

pub common: GameConfigCommon,
pub common: GameConfigCommon<'static, PtrRef>,

data: GameConfigData,
}

impl GameConfig {
/// Create a `GameConfig` from the provided [`GamePrototype`].
pub fn new(proto: GamePrototype) -> Result<Self, ValidationError> {
pub fn new(proto: GamePrototype<'_, StringRef>) -> Result<Self, ValidationError> {
// NOTE: If an error occurs then this function will leak memory. It's possible
// to fix this but there isn't currently a use case where this matters. If
// one comes up, then we'll fix it but otherwise it's cleaner to do it
// this way.

let missiles = ManuallyDrop::new(
proto
.missiles
.into_iter()
.enumerate()
.map(|(idx, m)| m.resolve().with(idx))
.collect::<Result<Vec<_>, _>>()
.with("missiles")?
.into_boxed_slice(),
);
let specials = ManuallyDrop::new(
proto
.specials
.into_iter()
.enumerate()
.map(|(idx, p)| p.resolve(&missiles).with(idx))
.collect::<Result<Vec<_>, _>>()
.with("specials")?
.into_boxed_slice(),
);
let planes = ManuallyDrop::new(
proto
.planes
.into_iter()
.enumerate()
.map(|(idx, p)| p.resolve(&missiles, &specials).with(idx))
.collect::<Result<Vec<_>, _>>()
.with("planes")?
.into_boxed_slice(),
);

let data = unsafe { GameConfigData::new(&planes, &missiles, &specials) };

let mut missiles = HashMap::new();
let mut planes = HashMap::new();
let mut specials = HashMap::new();

for (idx, missile) in proto.missiles.into_iter().enumerate() {
if missile.name.is_empty() {
return Err(
ValidationError::custom("name", "missile prototype had empty name")
.with(idx)
.with("missiles"),
);
}

if let Some(missile) = missiles.insert(missile.name.to_string(), missile) {
for missile in data.missiles() {
if missiles.insert(&*missile.name, missile).is_some() {
return Err(
ValidationError::custom("name", "multiple missile prototypes had the same name")
.with(missile.name.into_owned())
.with(missile.name.to_string())
.with("missiles"),
);
}
}

for (idx, special) in proto.specials.into_iter().enumerate() {
if special.name.is_empty() {
return Err(
ValidationError::custom("name", "special prototype had empty name")
.with(idx)
.with("specials"),
);
}

if let SpecialPrototypeData::Multishot(multishot) = &special.data {
if !missiles.contains_key(&*multishot.missile) {
return Err(ValidationError::custom(
"missile",
format_args!(
"multishot special refers to nonexistant missile prototype `{}`",
multishot.missile
),
))
.with(special.name.into_owned())
.with("specials");
}
}

if let Some(special) = specials.insert(special.name.to_string(), special) {
for special in data.specials() {
if specials.insert(&*special.name, special).is_some() {
return Err(
ValidationError::custom("name", "multiple missile prototypes had the same name")
.with(special.name.into_owned())
ValidationError::custom("name", "multiple special prototypes had the same name")
.with(special.name.to_string())
.with("specials"),
);
}
}

for (idx, plane) in proto.planes.into_iter().enumerate() {
if plane.name.is_empty() {
return Err(
ValidationError::custom("name", "plane prototype had empty name")
.with(idx)
.with("planes"),
);
}

if !missiles.contains_key(&*plane.missile) {
return Err(
ValidationError::custom(
"missile",
format_args!(
"plane prototype refers to a nonexistant missile prototype `{}`",
plane.missile
),
)
.with(plane.name.into_owned())
.with("planes"),
);
}

if !specials.contains_key(&*plane.special) {
return Err(
ValidationError::custom(
"special",
format_args!(
"plane prototype refers to nonexistant special prototype `{}`",
plane.special
),
)
.with(plane.name.into_owned())
.with("planes"),
);
}

if let Some(plane) = planes.insert(plane.name.to_string(), plane) {
for plane in data.planes() {
if planes.insert(&*plane.name, plane).is_some() {
return Err(
ValidationError::custom("name", "multiple missile prototypes had the same name")
.with(plane.name.into_owned())
.with("plane"),
ValidationError::custom("name", "multiple plane prototypes had the same name")
.with(plane.name.to_string())
.with("specials"),
);
}
}

if !planes.contains_key(&*proto.common.default_plane) {
return Err(ValidationError::custom(
"default_plane",
format_args!(
"default_plane refers to a plane prototype `{}` which does not exist",
proto.common.default_plane
),
));
}

Ok(Self {
missiles,
planes,
specials,

common: proto.common,
common: proto.common.resolve(data.planes())?,
data,
})
}

/// Purposefully leak this `GameConfig` in order to allow for static
/// references to be stored easily within the server datastructures.
pub fn leak(self) -> &'static mut Self {
Box::leak(Box::new(self))
fn into_data(self) -> GameConfigData {
self.data
}

/// Unsafelly reclaim and free a static reference that was created by calling
Expand All @@ -152,8 +118,8 @@ impl GameConfig {
/// # Safety
/// - The reference to `self` must never be used again after this method is
/// called.
pub unsafe fn reclaim(&'static mut self) {
let _ = Box::from_raw(self as *const Self as *mut Self);
pub unsafe fn reclaim(self) {
self.into_data().reclaim();
}
}

Expand All @@ -163,16 +129,16 @@ impl Default for GameConfig {
}
}

impl TryFrom<GamePrototype> for GameConfig {
impl TryFrom<GamePrototype<'_, StringRef>> for GameConfig {
type Error = ValidationError;

fn try_from(value: GamePrototype) -> Result<Self, Self::Error> {
fn try_from(value: GamePrototype<'_, StringRef>) -> Result<Self, Self::Error> {
Self::new(value)
}
}

impl Deref for GameConfig {
type Target = GameConfigCommon;
type Target = GameConfigCommon<'static, PtrRef>;

fn deref(&self) -> &Self::Target {
&self.common
Expand All @@ -184,3 +150,60 @@ impl DerefMut for GameConfig {
&mut self.common
}
}

#[derive(Clone)]
struct GameConfigData {
planes: NonNull<[PlanePrototype<'static, PtrRef>]>,
missiles: NonNull<[MissilePrototype]>,
specials: NonNull<[SpecialPrototype<'static, PtrRef>]>,
}

impl GameConfigData {
/// Create a set of GameConfigData.
///
/// # Safety
/// None of the prototypes may refer to any non-static data outside of that
/// being passed in here. The slices must not be dropped except by safely
/// calling [`reclaim`] once it is safe to do so.
unsafe fn new(
planes: &[PlanePrototype<PtrRef>],
missiles: &[MissilePrototype],
specials: &[SpecialPrototype<PtrRef>],
) -> Self {
Self {
planes: NonNull::new(planes as *const _ as *mut _).unwrap(),
missiles: NonNull::new(missiles as *const _ as *mut _).unwrap(),
specials: NonNull::new(specials as *const _ as *mut _).unwrap(),
}
}

/// Free all the memory that had been previously leaked.
///
/// # Safety
/// There must be no existing references to any of the data stored within this
/// type or else those references will be left as dangling references.
unsafe fn reclaim(self) {
// Note: Order matters here!
let _ = Box::from_raw(self.planes.as_ptr());
let _ = Box::from_raw(self.specials.as_ptr());
let _ = Box::from_raw(self.missiles.as_ptr());
}

fn planes(&self) -> &'static [PlanePrototype<'static, PtrRef>] {
unsafe { self.planes.as_ref() }
}

fn missiles(&self) -> &'static [MissilePrototype] {
unsafe { self.missiles.as_ref() }
}

fn specials(&self) -> &'static [SpecialPrototype<'static, PtrRef>] {
unsafe { self.specials.as_ref() }
}
}

impl fmt::Debug for GameConfigData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("..")
}
}
Loading