use std::sync::{Mutex, MutexGuard};
use crate::abi::{
ActiveBuff, AltarInfo, AltarLimitFn, AltarZoneSize, BedCheckFn, BuffTypeInfo, BuildCheckFn,
ComfortFn, ConfigCheckFn, CraftCheckFn, DamageFilterFn, DaytimeInfo, Event, EventFn,
ExperiencePassFn, GameEventFn, Header, HostApi, InteractionFn, InventoryResultFn,
KnowledgeInfo, KnowledgeInputFn, Level, MidHookFn, ModEventFn, NetMessageFn, RenameCallFn,
ResourceKey, ResourcePatchFn, SkillCheckFn, SpawnRequest, Status, StatusFn, Str, SummonFn,
SurvivorLookupFn, SurvivorStatusFn, TeleportRequest, TerrainEdit, TerrainMaterialInfo,
UiAnswerFn, UiFrameFn, WeatherFront, ZoneCheckFn,
};
use crate::locks::Relock;
use super::{FakeHost, LinkedHook, ServedResource, SubmittedRequest};
mod build;
mod content;
mod gameplay;
mod hooks;
mod inventory;
mod mods;
mod net;
mod services;
mod ui;
mod world;
use build::{
build, caller_frame, constant, data_dir, field_offset, log_level, symbol, system, type_hash,
type_size,
};
use content::{
add_text, content_read, content_serve, entity_data_open, game_text, icons_ready, item_data_get,
item_data_open, item_data_set, rebuild_resource, recipe_disabled, recipe_kind, recipe_rule,
resource_read, serve_resource,
};
use gameplay::{
account_knowledge, altar_limit, altar_zones_control, altars, altars_open, apply_buff,
base_zones, buff_types, buffs, build_filter, comfort_filter, craft_filter, damage_filter,
experience_filter, grant_knowledge, interaction_filter, knowledge, knowledge_input_filter,
quests, raise_base_limit, rate, remove_buff, revoke_knowledge, set_altar_zones, set_rate,
skill_override, summon_filter, summon_item, summoned, survivor_bed_check, survivor_lookup,
survivor_status, zone_check_filter,
};
use hooks::{code_range, hook, hook_at, patch_code, workshop_recipes};
use inventory::{inventory_open, inventory_submit, item_info};
use mods::{
config_check, config_schema, fault, join_missing, language, log, migrate, mods, replaced,
retired_command, settings, sync, texts,
};
use net::{net_channel, net_ready, net_send};
pub(crate) use services::view;
use services::{
declare_content, define_event, publish, send_event, service, subscribe, subscribe_event,
};
use ui::{ui_dialog, ui_frame, ui_hud, ui_markers, ui_widget};
use world::{
actions, chat, component, component_write, destroy, emit, entities, entity_template,
event_types, feature, forget_game_settings, game_event, game_settings, hosting,
keep_game_settings, notify, player, players, rename_filter, run_console, save_world,
server_world, set_game_settings, set_label, set_time_of_day, set_time_speed, set_weather,
spawn, status, teleport, terrain_edit, terrain_materials, time_of_day, time_speed_control,
weather_at, weather_fronts, world_key,
};
pub(crate) struct OpenRequest {
pub(crate) request: SubmittedRequest,
pub(crate) done: InventoryResultFn,
pub(crate) user: usize,
}
pub(crate) type ModEventSubscription = (String, Vec<(String, u64)>, ModEventFn, usize);
pub(crate) struct State {
pub(crate) host: FakeHost,
pub(crate) logs: Vec<(Level, String)>,
pub(crate) faults: Vec<String>,
pub(crate) hooks: Vec<LinkedHook>,
pub(crate) mid_hooks: Vec<(u64, MidHookFn, usize)>,
pub(crate) subscriptions: Vec<(Event, EventFn, usize)>,
pub(crate) game_events: Vec<(u32, GameEventFn, usize)>,
pub(crate) served: Vec<(ServedResource, ResourcePatchFn, usize)>,
pub(crate) rebuilds: Vec<ResourceKey>,
pub(crate) recipe_rules: Vec<(crate::abi::RecipeRuleKind, String)>,
pub(crate) texts: Vec<(u32, u32, String)>,
pub(crate) code_patches: Vec<(u64, Vec<u8>, Vec<u8>)>,
pub(crate) published: Vec<super::FakeService>,
pub(crate) defined_events: Vec<(String, Vec<(String, u64)>)>,
pub(crate) mod_event_subscriptions: Vec<ModEventSubscription>,
pub(crate) sent_events: Vec<String>,
pub(crate) migrations: Option<super::FakeMigrations>,
pub(crate) content_ids: Vec<String>,
pub(crate) defaults: Vec<String>,
pub(crate) synced: Vec<String>,
pub(crate) actions: Option<(crate::abi::ActionFn, usize)>,
pub(crate) chat: Vec<(u64, String)>,
pub(crate) labels: Vec<(u32, String)>,
pub(crate) rename_filters: Vec<(RenameCallFn, usize)>,
pub(crate) inventory_requests: Vec<OpenRequest>,
pub(crate) status_sources: Vec<(StatusFn, usize)>,
pub(crate) features: Vec<(String, Option<String>)>,
pub(crate) console: Vec<String>,
pub(crate) destroyed: Vec<u32>,
pub(crate) saves: usize,
pub(crate) writes: Vec<(u32, u32)>,
pub(crate) emits: Vec<(u32, Vec<u8>)>,
pub(crate) teleports: Vec<TeleportRequest>,
pub(crate) spawns: Vec<(u32, SpawnRequest)>,
pub(crate) craft_filters: Vec<(CraftCheckFn, usize)>,
pub(crate) channels: Vec<(String, u32, NetMessageFn, usize)>,
pub(crate) net_sent: Vec<(u32, u64, Vec<u8>)>,
pub(crate) notices: Vec<(u64, String)>,
pub(crate) build_filters: Vec<(BuildCheckFn, usize)>,
pub(crate) daytime: Option<DaytimeInfo>,
pub(crate) clock_speed: Option<f32>,
pub(crate) game_settings: Option<(Vec<u8>, u32)>,
pub(crate) kept_settings: Vec<Option<u8>>,
pub(crate) damage_filters: Vec<(DamageFilterFn, usize)>,
pub(crate) knowledge_input_filters: Vec<(KnowledgeInputFn, usize)>,
pub(crate) buff_types: Vec<BuffTypeInfo>,
pub(crate) buffs: std::collections::HashMap<u32, Vec<ActiveBuff>>,
pub(crate) knowledge: Vec<(u32, KnowledgeInfo)>,
pub(crate) grants: Vec<(u32, u32, u32, u32)>,
pub(crate) revokes: Vec<(u32, u32, u32, u32)>,
pub(crate) account_changes: Vec<(u64, u32, u32, u32, bool)>,
pub(crate) quests: Vec<u32>,
pub(crate) weather: Option<([f32; 4], Vec<WeatherFront>)>,
pub(crate) forecasts: Vec<(u32, i64)>,
pub(crate) terrain: Option<Vec<TerrainMaterialInfo>>,
pub(crate) terrain_edits: Vec<TerrainEdit>,
pub(crate) config_schema: Option<String>,
pub(crate) frames: Vec<(UiFrameFn, usize)>,
pub(crate) huds: Vec<(UiFrameFn, usize)>,
pub(crate) hud: Option<(u64, Vec<(u32, super::Drawn)>)>,
pub(crate) markers: Vec<super::FakeMarker>,
pub(crate) config_check: Option<(ConfigCheckFn, usize)>,
pub(crate) published_texts: Option<String>,
pub(crate) frame: Option<(u64, bool, Vec<super::Drawn>)>,
pub(crate) inputs: Vec<(String, super::Input)>,
pub(crate) dialogs: Vec<(super::ShownDialog, UiAnswerFn, usize)>,
world_key: Option<Box<str>>,
pub(crate) altars: Vec<AltarInfo>,
pub(crate) altars_opened: bool,
pub(crate) zone_control: bool,
pub(crate) zone_sizes: Vec<AltarZoneSize>,
pub(crate) zone_filters: Vec<(ZoneCheckFn, usize)>,
pub(crate) survivor_lookup: Option<(SurvivorLookupFn, usize)>,
pub(crate) bed_checks: Vec<(BedCheckFn, usize)>,
pub(crate) summons: Vec<(SummonFn, usize)>,
pub(crate) summoned: Vec<(SummonFn, usize)>,
pub(crate) item_kinds: Vec<String>,
pub(crate) content: std::collections::BTreeMap<[u32; 4], Vec<u8>>,
pub(crate) item_values: std::collections::BTreeMap<(u32, u32), (u32, Vec<u8>)>,
pub(crate) summon_items: Vec<(u32, u32)>,
pub(crate) stats: super::stats::FakeStats,
pub(crate) points: super::points::FakePoints,
pub(crate) survivor_status: Option<(SurvivorStatusFn, usize)>,
pub(crate) interaction_filters: Vec<(InteractionFn, usize)>,
pub(crate) skills: Vec<(u32, SkillCheckFn, usize)>,
pub(crate) comfort_filters: Vec<(ComfortFn, usize)>,
pub(crate) rates: Vec<(crate::abi::RateId, f32)>,
pub(crate) experience_filters: Vec<(ExperiencePassFn, usize)>,
pub(crate) altar_limit: Option<(AltarLimitFn, usize)>,
pub(crate) base_limit_raised: bool,
pub(crate) grouping: Option<(u32, u32)>,
run_out: Option<Box<str>>,
settings: Vec<Box<str>>,
sync_out: Vec<Box<str>>,
}
static STATE: Mutex<Option<State>> = Mutex::new(None);
static TURN: Mutex<()> = Mutex::new(());
pub(crate) fn state() -> MutexGuard<'static, Option<State>> {
STATE.relock()
}
thread_local! {
static HOLDS_TURN: core::cell::Cell<bool> = const { core::cell::Cell::new(false) };
}
fn take_turn() -> MutexGuard<'static, ()> {
assert!(
!HOLDS_TURN.get(),
"a fake host is already installed in this thread; drop the first `Installed` before the next install"
);
let turn = TURN.relock();
HOLDS_TURN.set(true);
turn
}
pub(crate) fn install(host: FakeHost) -> MutexGuard<'static, ()> {
let turn = take_turn();
*state() = Some(State {
host,
logs: Vec::new(),
faults: Vec::new(),
hooks: Vec::new(),
mid_hooks: Vec::new(),
subscriptions: Vec::new(),
game_events: Vec::new(),
served: Vec::new(),
rebuilds: Vec::new(),
recipe_rules: Vec::new(),
texts: Vec::new(),
code_patches: Vec::new(),
published: Vec::new(),
defined_events: Vec::new(),
mod_event_subscriptions: Vec::new(),
sent_events: Vec::new(),
migrations: None,
content_ids: Vec::new(),
defaults: Vec::new(),
synced: Vec::new(),
inventory_requests: Vec::new(),
sync_out: Vec::new(),
actions: None,
chat: Vec::new(),
labels: Vec::new(),
rename_filters: Vec::new(),
status_sources: Vec::new(),
features: Vec::new(),
console: Vec::new(),
destroyed: Vec::new(),
saves: 0,
writes: Vec::new(),
emits: Vec::new(),
teleports: Vec::new(),
spawns: Vec::new(),
craft_filters: Vec::new(),
daytime: None,
clock_speed: None,
game_settings: None,
kept_settings: Vec::new(),
damage_filters: Vec::new(),
knowledge_input_filters: Vec::new(),
buff_types: Vec::new(),
buffs: std::collections::HashMap::new(),
knowledge: Vec::new(),
grants: Vec::new(),
revokes: Vec::new(),
account_changes: Vec::new(),
quests: Vec::new(),
weather: None,
forecasts: Vec::new(),
terrain: None,
terrain_edits: Vec::new(),
config_schema: None,
channels: Vec::new(),
net_sent: Vec::new(),
notices: Vec::new(),
frames: Vec::new(),
huds: Vec::new(),
hud: None,
markers: Vec::new(),
config_check: None,
published_texts: None,
frame: None,
inputs: Vec::new(),
dialogs: Vec::new(),
build_filters: Vec::new(),
world_key: None,
altars: Vec::new(),
altars_opened: false,
zone_control: false,
zone_sizes: Vec::new(),
zone_filters: Vec::new(),
survivor_lookup: None,
bed_checks: Vec::new(),
summons: Vec::new(),
summoned: Vec::new(),
item_kinds: Vec::new(),
content: std::collections::BTreeMap::new(),
item_values: std::collections::BTreeMap::new(),
summon_items: Vec::new(),
stats: super::stats::FakeStats::default(),
points: super::points::FakePoints::default(),
survivor_status: None,
interaction_filters: Vec::new(),
skills: Vec::new(),
comfort_filters: Vec::new(),
rates: Vec::new(),
experience_filters: Vec::new(),
altar_limit: None,
base_limit_raised: false,
grouping: None,
run_out: None,
settings: Vec::new(),
});
turn
}
pub(crate) fn uninstall() {
*state() = None;
HOLDS_TURN.set(false);
}
pub(crate) fn with<R>(default: R, f: impl FnOnce(&mut State) -> R) -> R {
state().as_mut().map_or(default, f)
}
pub(crate) fn put<T>(out: *mut T, value: T) -> Status {
match unsafe { out.as_mut() } {
Some(slot) => {
*slot = value;
Status::OK
}
None => Status::BAD_ARGS,
}
}
pub(crate) fn found<T>(value: Option<T>, out: *mut T) -> Status {
value.map_or(Status::MISSING, |value| put(out, value))
}
pub(crate) fn text(s: Str) -> String {
unsafe { s.as_str() }.map_or_else(|| "<not UTF-8>".to_owned(), str::to_owned)
}
pub(crate) fn put_list<T: Copy>(
items: &[T],
out: *mut T,
capacity: u32,
count: *mut u32,
) -> Status {
if count.is_null() {
return Status::BAD_ARGS;
}
if !out.is_null() {
for (i, item) in items.iter().take(capacity as usize).enumerate() {
let slot = unsafe { out.add(i) };
unsafe { slot.write(*item) };
}
}
unsafe { count.write(u32::try_from(items.len()).unwrap_or(u32::MAX)) };
Status::OK
}
pub(crate) static TABLE: HostApi = HostApi {
header: Header::of::<HostApi>(),
host_version: Str::new("0.0.0-test"),
build,
symbol,
system,
field_offset,
type_size,
hook,
caller_frame,
fault,
log,
settings,
publish,
service,
constant,
data_dir,
log_level,
subscribe,
type_hash,
component,
entity_template,
player,
players,
serve_resource,
add_text,
mods,
sync,
code_range,
patch_code,
workshop_recipes,
hosting,
server_world,
retired_command,
chat,
set_label,
rename_filter,
join_missing,
hook_at,
inventory_open,
item_info,
inventory_submit,
status,
run_console,
altars_open,
altars,
altar_zones_control,
set_altar_zones,
zone_check_filter,
survivor_lookup,
survivor_bed_check,
summon_filter,
summon_item,
survivor_status,
interaction_filter,
skill_override,
comfort_filter,
experience_filter,
altar_limit,
raise_base_limit,
base_zones,
game_event,
entities,
destroy,
save_world,
world_key,
component_write,
emit,
teleport,
spawn,
craft_filter,
build_filter,
net_channel,
net_send,
net_ready,
notify,
time_of_day,
set_time_of_day,
time_speed_control,
set_time_speed,
game_settings,
set_game_settings,
damage_filter,
buff_types,
buffs,
apply_buff,
remove_buff,
knowledge,
grant_knowledge,
quests,
weather_at,
weather_fronts,
set_weather,
terrain_materials,
terrain_edit,
config_schema,
summoned,
item_data_open,
item_data_get,
item_data_set,
ui_frame,
ui_widget,
ui_dialog,
ui_hud,
ui_markers,
config_check,
texts,
content_serve,
content_read,
icons_ready,
stat_open: super::stats::stat_open,
stat_info: super::stats::stat_info,
stat_value: super::stats::stat_value,
stat_modify: super::stats::stat_modify,
stat_buff: super::stats::stat_buff,
stats_changed: super::stats::stats_changed,
stat_sources: super::stats::stat_sources,
stat_watch: super::stats::stat_watch,
define_event,
subscribe_event,
send_event,
affix_open: super::stats::affix_open,
affix_roll: super::stats::affix_roll,
item_lines: super::stats::item_lines,
affix_info: super::stats::affix_info,
migrate,
declare_content,
entity_data_open,
replaced,
knowledge_input_filter,
language,
keep_game_settings,
forget_game_settings,
revoke_knowledge,
account_knowledge,
resource_read,
game_text,
rebuild_resource,
recipe_rule,
recipe_disabled,
define_point: super::points::define_point,
attach_point: super::points::attach_point,
run_point: super::points::run_point,
actions,
event_types,
recipe_kind,
feature,
rate,
set_rate,
};