use std::collections::BTreeMap;
use std::collections::HashMap;
use std::sync::Arc;
use crate::nbt::{NbtMap, NbtValue};
use super::loss::{self, Direction};
use super::registry::Registry;
use super::types::MapExt;
use super::version::{encode_versions, EncodedVersion, MAX_STEP, V99};
pub type Converter = Box<dyn Fn(&mut NbtMap, EncodedVersion, EncodedVersion) + Send + Sync>;
pub type ValueConverter = Box<dyn Fn(&mut NbtValue, EncodedVersion, EncodedVersion) + Send + Sync>;
pub type Walker = Arc<dyn Fn(&Registry, &mut NbtMap, EncodedVersion, EncodedVersion) + Send + Sync>;
type HookFn = Box<dyn Fn(&mut NbtMap, EncodedVersion, EncodedVersion) + Send + Sync>;
type ValueHookFn = Box<dyn Fn(&mut NbtValue, EncodedVersion, EncodedVersion) + Send + Sync>;
pub struct Hook {
pub pre: Option<HookFn>,
pub post: Option<HookFn>,
}
pub struct ValueHook {
pub pre: Option<ValueHookFn>,
pub post: Option<ValueHookFn>,
}
#[inline]
fn floor<V>(map: &BTreeMap<EncodedVersion, V>, version: EncodedVersion) -> Option<&V> {
map.range(..=version).next_back().map(|(_, v)| v)
}
pub struct MCValueType {
pub name: &'static str,
converters: Vec<(EncodedVersion, ValueConverter)>,
reverse_converters: Vec<(EncodedVersion, ValueConverter)>,
hooks: BTreeMap<EncodedVersion, Vec<ValueHook>>,
}
impl MCValueType {
pub fn new(name: &'static str) -> Self {
Self {
name,
converters: Vec::new(),
reverse_converters: Vec::new(),
hooks: BTreeMap::new(),
}
}
pub fn add_converter(&mut self, version: i32, step: i32, converter: ValueConverter) {
self.converters
.push((encode_versions(version, step), converter));
}
pub fn add_reverse_converter(&mut self, version: i32, step: i32, converter: ValueConverter) {
self.reverse_converters
.push((encode_versions(version, step), converter));
}
pub fn add_structure_hook(&mut self, version: i32, step: i32, hook: ValueHook) {
self.hooks
.entry(encode_versions(version, step))
.or_default()
.push(hook);
}
pub fn finalize(&mut self) {
self.converters.sort_by_key(|(v, _)| *v);
self.reverse_converters.sort_by_key(|(v, _)| *v);
}
pub fn convert(&self, data: &mut NbtValue, from: EncodedVersion, to: EncodedVersion) {
match loss::direction() {
Direction::Forward => self.convert_forward(data, from, to),
Direction::Reverse => self.convert_reverse(data, from, to),
}
}
fn convert_forward(&self, data: &mut NbtValue, from: EncodedVersion, to: EncodedVersion) {
for (cv, converter) in &self.converters {
let cv = *cv;
if cv <= from {
continue;
}
if cv > to {
break;
}
if let Some(hooks) = floor(&self.hooks, cv) {
for h in hooks {
if let Some(pre) = &h.pre {
pre(data, from, to);
}
}
}
converter(data, from, to);
if let Some(hooks) = floor(&self.hooks, to) {
for h in hooks {
if let Some(post) = &h.post {
post(data, from, to);
}
}
}
}
}
fn convert_reverse(&self, data: &mut NbtValue, from: EncodedVersion, to: EncodedVersion) {
for (cv, converter) in self.reverse_converters.iter().rev() {
let cv = *cv;
if cv > from {
continue;
}
if cv <= to {
break;
}
converter(data, from, to);
}
}
}
pub struct DataType {
pub name: &'static str,
converters: Vec<(EncodedVersion, Converter)>,
reverse_converters: Vec<(EncodedVersion, Converter)>,
walkers: BTreeMap<EncodedVersion, Vec<Walker>>,
hooks: BTreeMap<EncodedVersion, Vec<Hook>>,
walkers_by_id: HashMap<String, BTreeMap<EncodedVersion, Vec<Walker>>>,
}
impl DataType {
pub fn new(name: &'static str) -> Self {
Self {
name,
converters: Vec::new(),
reverse_converters: Vec::new(),
walkers: BTreeMap::new(),
hooks: BTreeMap::new(),
walkers_by_id: HashMap::new(),
}
}
pub fn add_structure_converter(&mut self, version: i32, step: i32, converter: Converter) {
self.converters
.push((encode_versions(version, step), converter));
}
pub fn add_reverse_converter(&mut self, version: i32, step: i32, converter: Converter) {
self.reverse_converters
.push((encode_versions(version, step), converter));
}
pub fn add_converter_for_id(
&mut self,
id: &'static str,
version: i32,
step: i32,
converter: Converter,
) {
self.add_structure_converter(
version,
step,
Box::new(move |data, from, to| {
if data.get_string("id") == Some(id) {
converter(data, from, to);
}
}),
);
}
pub fn add_reverse_converter_for_id(
&mut self,
id: &'static str,
version: i32,
step: i32,
converter: Converter,
) {
self.add_reverse_converter(
version,
step,
Box::new(move |data, from, to| {
if data.get_string("id") == Some(id) {
converter(data, from, to);
}
}),
);
}
pub fn add_structure_walker(&mut self, version: i32, step: i32, walker: Walker) {
self.walkers
.entry(encode_versions(version, step))
.or_default()
.push(walker);
}
pub fn add_walker(&mut self, version: i32, step: i32, id: &str, walker: Walker) {
self.walkers_by_id
.entry(id.to_string())
.or_default()
.entry(encode_versions(version, step))
.or_default()
.push(walker);
}
pub fn copy_walkers(&mut self, version: i32, step: i32, from_id: &str, to_id: &str) {
let key = encode_versions(version, step);
let to_copy: Vec<Walker> = match self.walkers_by_id.get(from_id) {
Some(per_version) => match floor(per_version, key) {
Some(list) => list.clone(),
None => return,
},
None => return,
};
for w in to_copy {
self.add_walker(version, step, to_id, w);
}
}
pub fn add_structure_hook(&mut self, version: i32, step: i32, hook: Hook) {
self.hooks
.entry(encode_versions(version, step))
.or_default()
.push(hook);
}
pub fn finalize(&mut self) {
self.converters.sort_by_key(|(v, _)| *v);
self.reverse_converters.sort_by_key(|(v, _)| *v);
}
fn run_pre_hooks(
&self,
at: EncodedVersion,
data: &mut NbtMap,
from: EncodedVersion,
to: EncodedVersion,
) {
if let Some(hooks) = floor(&self.hooks, at) {
for h in hooks {
if let Some(pre) = &h.pre {
pre(data, from, to);
}
}
}
}
fn run_post_hooks(
&self,
at: EncodedVersion,
data: &mut NbtMap,
from: EncodedVersion,
to: EncodedVersion,
) {
if let Some(hooks) = floor(&self.hooks, at) {
for h in hooks.iter().rev() {
if let Some(post) = &h.post {
post(data, from, to);
}
}
}
}
pub fn convert(
&self,
reg: &Registry,
data: &mut NbtMap,
from: EncodedVersion,
to: EncodedVersion,
) {
match loss::direction() {
Direction::Forward => self.convert_forward(reg, data, from, to),
Direction::Reverse => self.convert_reverse(reg, data, from, to),
}
}
fn convert_forward(
&self,
reg: &Registry,
data: &mut NbtMap,
from: EncodedVersion,
to: EncodedVersion,
) {
for (cv, converter) in &self.converters {
let cv = *cv;
if cv <= from {
continue;
}
if cv > to {
break;
}
self.run_pre_hooks(cv, data, from, to);
converter(data, from, to);
self.run_post_hooks(to, data, from, to);
}
self.run_pre_hooks(to, data, from, to);
if let Some(walkers) = floor(&self.walkers, to) {
for w in walkers {
w(reg, data, from, to);
}
}
if !self.walkers_by_id.is_empty() {
if let Some(id) = data.get_string("id").map(|s| s.to_string()) {
if let Some(per_version) = self.walkers_by_id.get(&id) {
if let Some(walkers) = floor(per_version, to) {
for w in walkers {
w(reg, data, from, to);
}
}
}
}
}
self.run_post_hooks(to, data, from, to);
}
fn convert_reverse(
&self,
reg: &Registry,
data: &mut NbtMap,
from: EncodedVersion,
to: EncodedVersion,
) {
self.run_pre_hooks(from, data, from, to);
if let Some(walkers) = floor(&self.walkers, from) {
for w in walkers {
w(reg, data, from, to);
}
}
if !self.walkers_by_id.is_empty() {
if let Some(id) = data.get_string("id").map(|s| s.to_string()) {
if let Some(per_version) = self.walkers_by_id.get(&id) {
if let Some(walkers) = floor(per_version, from) {
for w in walkers {
w(reg, data, from, to);
}
}
}
}
}
for (cv, converter) in self.reverse_converters.iter().rev() {
let cv = *cv;
if cv > from {
continue;
}
if cv <= to {
break;
}
converter(data, from, to);
}
self.run_post_hooks(from, data, from, to);
}
}
pub fn walk_with_breakpoints(
breakpoints: &[EncodedVersion],
from: EncodedVersion,
to: EncodedVersion,
mut apply: impl FnMut(EncodedVersion, EncodedVersion),
) {
let mut current = from;
for &bp in breakpoints {
if current >= bp {
continue;
}
let seg_to = to.min(bp - 1);
apply(current, seg_to);
current = seg_to;
if current == to {
break;
}
}
if current != to {
apply(current, to);
}
}
pub fn walk_with_breakpoints_reverse(
breakpoints: &[EncodedVersion],
from: EncodedVersion,
to: EncodedVersion,
mut apply: impl FnMut(EncodedVersion, EncodedVersion),
) {
let mut segments: Vec<(EncodedVersion, EncodedVersion)> = Vec::new();
walk_with_breakpoints(breakpoints, to, from, |seg_lo, seg_hi| {
segments.push((seg_lo, seg_hi));
});
for (seg_lo, seg_hi) in segments.into_iter().rev() {
apply(seg_hi, seg_lo);
}
}
#[inline]
pub fn encode_endpoints(
from_data_version: i32,
to_data_version: i32,
) -> (EncodedVersion, EncodedVersion) {
(
encode_versions(from_data_version.max(V99), MAX_STEP),
encode_versions(to_data_version, MAX_STEP),
)
}