using System; using System.IO; using System.Collections.Generic; using metagen; using game; public delegate void ConfigCb(ConfEntry ce); public delegate void ConfigCbGeneric(T conf) where T : ConfBase; public delegate bool ConfigCbGenericInterruptible(T conf) where T : ConfBase; public class ConfEntry { public const int FMT_BIN = 0; public const int FMT_LZ4 = 1; public const int FMT_FILE_REF = 2; public const int FMT_LZ4_REF = 3; public const byte ST_UNRESOLVED = 0; public const byte ST_RESOLVED = 1; public const byte ST_MARK_RELEASED = 2; public uint format; public uint id; public uint class_id; public long stream_pos; public int stream_size; public System.Type class_type { get { return AutogenBundle.id2type(class_id); } } //NOTE: mostly for internal usage, should not be used directly in most cases public ConfBase _conf; public byte status; } public class Configs { public const byte BIN_REGULAR = 1; Stream source; //for less GC pressure Lz4DecoderStream lz_decoder = new Lz4DecoderStream(); MemoryStream lz_stream = new MemoryStream(); MemoryStream lz_dst_stream = new MemoryStream(); MemoryStream entry_stream = new MemoryStream(); MsgPackDataReader entry_msgp_reader; Dictionary ids = new Dictionary(); Dictionary strids = new Dictionary(); List data = new List(); int resolved = 0; public int Count { get { return data.Count; } } public int Resolved { get { return resolved; } } public bool strict_loading_verification = true; public int stats_lz4 = 0; public int stats_bin = 0; public int stats_max_buf = 0; public const uint MIN_GEN_ID = 1 << 29; static uint last_gen_id = MIN_GEN_ID; public static uint GenId() { return last_gen_id++; } public ConfEntry this[int i] { get { return data[i]; } } static byte[] hash_bytes = new byte[256]; static public uint ID(string id) { //NOTE: avoiding memory allocations: using static memory buffer, // avoding string contacation System.Text.Encoding.ASCII.GetBytes(id, 1, id.Length-1, hash_bytes, 0); System.Text.Encoding.ASCII.GetBytes(".conf.js", 0, 8, hash_bytes, id.Length-1); uint hash = Crc32.Compute(hash_bytes, id.Length+8-1) & 0xFFFFFFF; return hash; } public uint ValidID(string strid) { var ce = FindEntry(strid); if(ce != null) return ce.id; Error.Verify(false, "Invalid config: {0}", strid); return 0; } public string StrID(uint id) { return Get(id).strid; } bool Check(bool condition, string msg = "") { if(strict_loading_verification) Error.Verify(condition, msg); if(!condition) Log.Warn("Loading failed! "+msg); return condition; } public bool Load(Stream source_) { if(source != null) source.Close(); source = source_; source.Position = 0; entry_msgp_reader = new MsgPackDataReader(entry_stream); var bin_reader = new BinaryReader(source); uint binary_format = 0; uint version = 0; uint header_size = 0; try { binary_format = bin_reader.ReadByte(); version = bin_reader.ReadUInt32(); header_size = bin_reader.ReadUInt32(); Log.Info("Configs format: " + binary_format + ", version: " + version + ", header size:" + header_size); } catch(Exception e) { Check(false, "Header read error: " + e.Message); return false; } uint header_offset = 1 + 4 + 4 + header_size; if(binary_format != BIN_REGULAR) { Check(false, "Unknown format"); return false; } var msgp_reader = new MsgPackDataReader(source); if(!Check(msgp_reader.BeginContainer() == 0)) return false; int total_configs = 0; if(!Check(msgp_reader.GetContainerSize(ref total_configs) == 0)) return false; Log.Info("Loading configs: " + total_configs); while(total_configs-- > 0) { if(!Check(msgp_reader.BeginContainer() == 0, "bad array")) return false; uint format = 0; if(!Check(msgp_reader.ReadU32(ref format) == 0, "bad format")) return false; if(format == ConfEntry.FMT_LZ4 || format == ConfEntry.FMT_LZ4_REF) stats_lz4++; else if(format == ConfEntry.FMT_BIN || format == ConfEntry.FMT_FILE_REF) stats_bin++; else { Check(false, "Unknown format: " + format); return false; } uint id = 0; if(!Check(msgp_reader.ReadU32(ref id) == 0, "bad id")) return false; if(id == 0) { Check(false, "0 id"); return false; } uint strid = 0; if(!Check(msgp_reader.ReadU32(ref strid) == 0, "bad strid")) return false; uint class_id = 0; if(!Check(msgp_reader.ReadU32(ref class_id) == 0, "bad classid")) return false; uint offset = 0; if(!Check(msgp_reader.ReadU32(ref offset) == 0, "bad offset")) return false; int size = 0; if(!Check(msgp_reader.ReadI32(ref size) == 0, "bad size")) return false; if(size == 0) { Check(false, "0 size for " + id); return false; } var ce = new ConfEntry(); ce._conf = null; ce.format = format; ce.id = id; ce.class_id = class_id; ce.stream_pos = header_offset + offset; ce.stream_size = size; int res = Add(id, ce, strid); if(res != 0) { Check(false, "Duplicate id: " + id + "(" + strid + ") " + res); return false; } if(!Check(msgp_reader.EndContainer() == 0)) return false; } Log.Info("Loading configs done"); return true; } public T FindByStrID(string strid) where T : class { var ce = FindEntry(strid); if(ce == null) return null; return ResolveEntry(ce) as T; } public T FindByID(uint id) where T : class { var ce = FindEntry(id); if(ce == null) return null; return ResolveEntry(ce) as T; } public T Get(string strid) where T : ConfBase { T o = Find(strid); if(o == null) Error.Verify(false, "Invalid config: {0}", strid); return o; } public T Get(uint id) where T : ConfBase { T o = Find(id); if(o == null) Error.Verify(false, "Invalid config: {0}", id); return o; } public T Find(string strid) where T : ConfBase { return FindByStrID(strid); } public T Find(uint id) where T : ConfBase { return FindByID(id); } public void Release(ConfBase c) { if(c == null) return; var ce = FindEntry(c.id); if(ce == null || ce._conf == null) return; ce._conf = null; ce.status = ConfEntry.ST_UNRESOLVED; --resolved; } public void MarkReleased(ConfBase c) { if(c == null) return; var ce = FindEntry(c.id); if(ce == null || ce._conf == null) return; ce.status = ConfEntry.ST_MARK_RELEASED; } public void PurgeMarkedToRelease() { for(int i=0;i(uint id, bool exact_type_matching = true) { var ce = FindEntry(id); if(ce == null) return false; var type = typeof(T); if(ce.class_type == type) return true; return !exact_type_matching && ce.class_type.IsSubclassOf(type); } public System.Type TypeOf(uint id) { ConfEntry ce = FindEntry(id); return ce != null ? ce.class_type : null; } ConfBase ResolveEntry(ConfEntry ce) { if(ce._conf != null) return ce._conf; //Log.Debug("RESOLVE START: " + ce.id); byte[] res; int res_len; ResolveToBin(ce, out res, out res_len); entry_stream.SetData(res, 0, res_len); entry_msgp_reader.setPos(0); FillConfigEntry(ce, entry_msgp_reader, strict_loading_verification); ce.status = ConfEntry.ST_RESOLVED; ++resolved; //Log.Debug("RESOLVED: " + ce._conf.strid + " " + resolved); return ce._conf; } public ConfBase At(int i) { return ResolveEntry(data[i]); } void ResolveToBin(ConfEntry ce, out byte[] res, out int res_len) { res = null; res_len = 0; if(ce.format == ConfEntry.FMT_BIN) { var tmp_buf = TempBuffer.Get(ce.stream_size); source.Position = ce.stream_pos; source.Read(tmp_buf, 0, ce.stream_size); res = tmp_buf; res_len = ce.stream_size; } else if(ce.format == ConfEntry.FMT_LZ4) { var lz_buf = TempBuffer.Get(ce.stream_size); source.Position = ce.stream_pos; source.Read(lz_buf, 0, ce.stream_size); int lz_dest_size = (int)BitConverter.ToUInt32(lz_buf, 0); var dst_buf = TempBuffer.Get(lz_dest_size); lz_dst_stream.SetData(dst_buf, 0, dst_buf.Length); //taking into account first 4 bytes which store uncompressed size lz_stream.SetData(lz_buf, 4, ce.stream_size-4); lz_decoder.Reset(lz_stream); lz_decoder.CopyToEx(lz_dst_stream); res = lz_dst_stream.GetBuffer(); res_len = (int)lz_dst_stream.Position; } else if(ce.format == ConfEntry.FMT_FILE_REF) { var tmp_buf = TempBuffer.Get(ce.stream_size); source.Position = ce.stream_pos; source.Read(tmp_buf, 0, ce.stream_size); string file_path = System.Text.Encoding.UTF8.GetString(tmp_buf, 0, ce.stream_size); //NOTE: since it's a dev.only method we don't care about extra mem.allocs var file_bytes = File.ReadAllBytes(file_path); res = file_bytes; res_len = file_bytes.Length; } else Error.Assert(false, "Unknown entry format: " + ce.format); } static void FillConfigEntry(ConfEntry ce, IDataReader reader, bool strict_verification) { var mstruct = AutogenBundle.createById(ce.class_id); Error.Verify(mstruct != null); ce._conf = ReadConf(mstruct, reader, strict_verification, ce.id); if(ce._conf != null) ce._conf.id = ce.id; } static ConfBase ReadConf(IMetaStruct mstruct, IDataReader reader, bool strict_verification, uint id) { var status = Meta.SyncSafe(MetaSyncContext.NewForRead(AutogenBundle.createById, reader), ref mstruct); bool success = status == 0; if(strict_verification) { if(!success) Error.Verify(false, "Bad config read " + id + " " + status); } else if(!success) return null; var conf = mstruct as ConfBase; if(conf == null) Error.Verify(false, "Bad config cast " + id); return conf; } public int Add(uint id, ConfEntry ce, uint strid = 0) { if(ids.ContainsKey(id)) return 1; if(strid != 0 && strids.ContainsKey(strid)) return 2; if(strid != 0) strids.Add(strid, data.Count); ids.Add(id, data.Count); data.Add(ce); return 0; } ConfEntry FindEntry(uint id) { int idx; if(!ids.TryGetValue(id, out idx)) return null; return data[idx]; } ConfEntry FindEntry(string strid) { int idx; if(!strids.TryGetValue(Hash.CRC32(strid), out idx)) return null; return data[idx]; } public bool Replace(uint id, ConfEntry ce) { int idx; if(!ids.TryGetValue(id, out idx)) return false; data[idx] = ce; return true; } public void Clear() { if(source != null) source.Close(); source = null; ids.Clear(); strids.Clear(); data.Clear(); resolved = 0; } public void Foreach(ConfigCb cb) { for(int i=0;i(ConfigCbGeneric cb) where T : ConfBase { var type = typeof(T); for(int i=0;i(ce.id); if(conf != null) cb(conf); } } public void Foreach(ConfigCbGenericInterruptible cb) where T : ConfBase { var type = typeof(T); for(int i=0;i(ce.id); if(conf == null) continue; if(!cb(conf)) return; } } public string[] GetList() { string[] list = new string[data.Count]; for (int i = 0; i < data.Count; i++) { list[i] = StrID(data[i].id); } return list; } } public static class ConfigsExtensions { static public void Load(this Configs configs, Stream stream) { configs.Clear(); configs.Load(stream); } }