mirror of
https://github.com/simtactics/mysimulation.git
synced 2025-07-04 13:47:04 -04:00
Removed NioTSO client and server
- NioTSO client isn't needed because we're using RayLib - Added FreeSO's API server to handle most backend operations
This commit is contained in:
parent
f12ba1502b
commit
22191ce648
591 changed files with 53264 additions and 3362 deletions
32
server/tso.common/Utils/AssemblyUtils.cs
Executable file
32
server/tso.common/Utils/AssemblyUtils.cs
Executable file
|
@ -0,0 +1,32 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
public class AssemblyUtils
|
||||
{
|
||||
public static Assembly Entry;
|
||||
public static List<Assembly> GetFreeSOLibs()
|
||||
{
|
||||
var map = new Dictionary<string, Assembly>();
|
||||
if (Entry == null) Entry = Assembly.GetEntryAssembly();
|
||||
RecurseAssembly(Entry, map);
|
||||
return map.Values.ToList();
|
||||
}
|
||||
|
||||
private static void RecurseAssembly(Assembly assembly, Dictionary<string, Assembly> map)
|
||||
{
|
||||
var refs = assembly.GetReferencedAssemblies();
|
||||
foreach (var refAsm in refs)
|
||||
{
|
||||
if ((refAsm.Name.StartsWith("FSO.") || refAsm.Name.Equals("FreeSO") || refAsm.Name.Equals("server")) && !map.ContainsKey(refAsm.Name))
|
||||
{
|
||||
var loadedAssembly = Assembly.Load(refAsm);
|
||||
map.Add(refAsm.Name, loadedAssembly);
|
||||
RecurseAssembly(loadedAssembly, map);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
126
server/tso.common/Utils/BBCodeParser.cs
Executable file
126
server/tso.common/Utils/BBCodeParser.cs
Executable file
|
@ -0,0 +1,126 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// In:
|
||||
/// - String Containing BBCode
|
||||
///
|
||||
/// [color=red]Hello![/color]
|
||||
/// [color=#FF0000]Also [size=20]red[/size][/color]
|
||||
/// \[ escaped \] square brackets.
|
||||
///
|
||||
/// Out:
|
||||
/// - String Without BBCode
|
||||
/// - Ordered list of BBCodeCommands, and the index in the converted string they occur at.
|
||||
/// </summary>
|
||||
public class BBCodeParser
|
||||
{
|
||||
public string Stripped;
|
||||
public List<BBCodeCommand> Commands = new List<BBCodeCommand>();
|
||||
|
||||
public BBCodeParser(string input)
|
||||
{
|
||||
var stripped = new StringBuilder();
|
||||
int index = 0;
|
||||
while (index < input.Length)
|
||||
{
|
||||
var newIndex = input.IndexOf('[', index);
|
||||
if (newIndex == -1)
|
||||
{
|
||||
newIndex = input.Length;
|
||||
//no more commands.
|
||||
//render the rest of the string and break out.
|
||||
stripped.Append(input.Substring(index, newIndex - index));
|
||||
break;
|
||||
}
|
||||
stripped.Append(input.Substring(index, newIndex - index));
|
||||
//we found the start of a bbcode. is it escaped?
|
||||
if (newIndex > 0 && input[newIndex - 1] == '\\')
|
||||
{
|
||||
//draw the bracket.
|
||||
stripped[stripped.Length - 1] = '['; //replace the backslash with the leftbracket
|
||||
index = newIndex + 1; //continue after the bracket
|
||||
} else
|
||||
{
|
||||
//find our right bracket
|
||||
var endIndex = input.IndexOf(']', newIndex);
|
||||
if (endIndex == -1)
|
||||
{
|
||||
//fail safe.
|
||||
stripped.Append('[');
|
||||
index = newIndex + 1; //continue after the bracket
|
||||
} else
|
||||
{
|
||||
Commands.Add(new BBCodeCommand(input.Substring(newIndex + 1, (endIndex - newIndex) - 1), stripped.Length));
|
||||
index = endIndex + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Stripped = stripped.ToString();
|
||||
}
|
||||
|
||||
public static string SanitizeBB(string input)
|
||||
{
|
||||
if (input.LastOrDefault() == '\\') input += ' ';
|
||||
return input.Replace("[", "\\[");
|
||||
}
|
||||
}
|
||||
|
||||
public class BBCodeCommand
|
||||
{
|
||||
public BBCodeCommandType Type;
|
||||
public string Parameter;
|
||||
public int Index;
|
||||
public bool Close;
|
||||
|
||||
public BBCodeCommand(string cmd, int index)
|
||||
{
|
||||
Index = index;
|
||||
if (cmd[0] == '/')
|
||||
{
|
||||
Close = true;
|
||||
cmd = cmd.Substring(1);
|
||||
}
|
||||
var split = cmd.Split('=');
|
||||
if (split.Length > 1) Parameter = split[1];
|
||||
System.Enum.TryParse(split[0], out Type);
|
||||
}
|
||||
|
||||
public Color ParseColor()
|
||||
{
|
||||
if (Parameter == null || Parameter.Length == 0) return Color.White;
|
||||
//todo: search color static members for named colours
|
||||
//for now we only support hex
|
||||
if (Parameter.Length == 7 && Parameter[0] == '#')
|
||||
{
|
||||
uint rgb;
|
||||
if (uint.TryParse(Parameter.Substring(1), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out rgb)) {
|
||||
return new Color((byte)(rgb >> 16), (byte)(rgb >> 8), (byte)(rgb), (byte)255);
|
||||
}
|
||||
} else
|
||||
{
|
||||
var color = typeof(Color).GetProperties().Where(x => x.PropertyType == typeof(Color) && x.Name.ToLowerInvariant() == Parameter.ToLowerInvariant()).FirstOrDefault();
|
||||
if (color != null)
|
||||
{
|
||||
return (Color)color.GetValue(null);
|
||||
}
|
||||
}
|
||||
return Color.White;
|
||||
}
|
||||
}
|
||||
|
||||
public enum BBCodeCommandType
|
||||
{
|
||||
unknown,
|
||||
color,
|
||||
size,
|
||||
emoji,
|
||||
s
|
||||
}
|
||||
}
|
29
server/tso.common/Utils/Base64JsonConverter.cs
Executable file
29
server/tso.common/Utils/Base64JsonConverter.cs
Executable file
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
public class Base64JsonConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
if (typeof(byte[]).IsAssignableFrom(objectType))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
string data = (string)reader.Value;
|
||||
return Convert.FromBase64String(data);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
byte[] bytes = (byte[])value;
|
||||
writer.WriteValue(Convert.ToBase64String(bytes));
|
||||
}
|
||||
}
|
||||
}
|
301
server/tso.common/Utils/Binding.cs
Executable file
301
server/tso.common/Utils/Binding.cs
Executable file
|
@ -0,0 +1,301 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
public class Binding <T> : IDisposable where T : INotifyPropertyChanged
|
||||
{
|
||||
private List<IBinding> Bindings = new List<IBinding>();
|
||||
private HashSet<INotifyPropertyChanged> Watching = new HashSet<INotifyPropertyChanged>();
|
||||
private T _Value;
|
||||
|
||||
public event Callback<T> ValueChanged;
|
||||
|
||||
public Binding()
|
||||
{
|
||||
lock(Binding.All)
|
||||
Binding.All.Add(this);
|
||||
}
|
||||
|
||||
~Binding()
|
||||
{
|
||||
//i don't think the garbage collector will remove this if there is still a reference to its callback function.
|
||||
ClearWatching();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ClearWatching();
|
||||
Bindings = null;
|
||||
}
|
||||
|
||||
public T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Value;
|
||||
}
|
||||
set
|
||||
{
|
||||
ClearWatching();
|
||||
_Value = value;
|
||||
if(_Value != null){
|
||||
Watch(_Value, null);
|
||||
}
|
||||
Digest();
|
||||
|
||||
if(ValueChanged != null)
|
||||
{
|
||||
ValueChanged(_Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearWatching()
|
||||
{
|
||||
lock (Watching)
|
||||
{
|
||||
var clone = new List<INotifyPropertyChanged>(Watching);
|
||||
foreach (var item in clone)
|
||||
{
|
||||
item.PropertyChanged -= OnPropertyChanged;
|
||||
Watching.Remove(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Watch(INotifyPropertyChanged source, HashSet<INotifyPropertyChanged> toWatch)
|
||||
{
|
||||
lock (Watching)
|
||||
{
|
||||
if (!Watching.Contains(source))
|
||||
{
|
||||
source.PropertyChanged += OnPropertyChanged;
|
||||
Watching.Add(source);
|
||||
}
|
||||
toWatch?.Add(source);
|
||||
|
||||
var properties = source.GetType().GetProperties();
|
||||
foreach (var property in properties)
|
||||
{
|
||||
if (typeof(INotifyPropertyChanged).IsAssignableFrom(property.PropertyType))
|
||||
{
|
||||
var value = (INotifyPropertyChanged)property.GetValue(source, null);
|
||||
if (value != null)
|
||||
{
|
||||
Watch(value, toWatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
lock (Watching)
|
||||
{
|
||||
var property = sender.GetType().GetProperty(e.PropertyName);
|
||||
if (typeof(INotifyPropertyChanged).IsAssignableFrom(property.PropertyType))
|
||||
{
|
||||
//We may have a new nested object to watch
|
||||
//ClearWatching();
|
||||
if (_Value != null)
|
||||
{
|
||||
var toWatch = new HashSet<INotifyPropertyChanged>();
|
||||
Watch(_Value, toWatch);
|
||||
|
||||
var toRemove = Watching.Except(toWatch).ToList();
|
||||
foreach (var rem in toRemove)
|
||||
{
|
||||
Watching.Remove(rem);
|
||||
rem.PropertyChanged -= OnPropertyChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Digest();
|
||||
}
|
||||
|
||||
//Using a dumb digest system for now, not very efficient but works
|
||||
private void Digest()
|
||||
{
|
||||
GameThread.InUpdate(() => _Digest());
|
||||
}
|
||||
|
||||
private void _Digest()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (Bindings == null) return;
|
||||
foreach (var binding in Bindings)
|
||||
{
|
||||
binding.Digest(_Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Binding<T> WithBinding(object target, string targetProperty, string sourcePath)
|
||||
{
|
||||
//If top level changes, we need to update children
|
||||
//If member of top level changes, we need to update children
|
||||
var binding = new DotPathBinding(target, targetProperty, DotPath.CompileDotPath(typeof(T), sourcePath));
|
||||
Bindings.Add(binding);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Binding<T> WithBinding(object target, string targetProperty, string sourcePath, Func<object, object> valueConverter)
|
||||
{
|
||||
//If top level changes, we need to update children
|
||||
//If member of top level changes, we need to update children
|
||||
var binding = new DotPathBinding(target, targetProperty, DotPath.CompileDotPath(typeof(T), sourcePath), valueConverter);
|
||||
Bindings.Add(binding);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Binding<T> WithMultiBinding(Callback<BindingChange[]> callback, params string[] paths)
|
||||
{
|
||||
var compiledPaths = new PropertyInfo[paths.Length][];
|
||||
for(int i=0; i < paths.Length; i++){
|
||||
compiledPaths[i] = DotPath.CompileDotPath(typeof(T), paths[i]);
|
||||
}
|
||||
|
||||
var binding = new MultiDotPathBinding(callback, paths, compiledPaths);
|
||||
Bindings.Add(binding);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public class BindingChange
|
||||
{
|
||||
public string Path;
|
||||
public object PreviousValue;
|
||||
public object Value;
|
||||
}
|
||||
|
||||
class MultiDotPathBinding : IBinding
|
||||
{
|
||||
private Callback<BindingChange[]> Callback;
|
||||
private PropertyInfo[][] Paths;
|
||||
private string[] PathStrings;
|
||||
private object[] Values;
|
||||
|
||||
public MultiDotPathBinding(Callback<BindingChange[]> callback, string[] pathStrings, PropertyInfo[][] paths)
|
||||
{
|
||||
Callback = callback;
|
||||
Paths = paths;
|
||||
PathStrings = pathStrings;
|
||||
Values = new object[Paths.Length];
|
||||
}
|
||||
|
||||
public void Digest(object source)
|
||||
{
|
||||
List<BindingChange> changes = null;
|
||||
for(int i=0; i < Paths.Length; i++){
|
||||
var path = Paths[i];
|
||||
var value = DotPath.GetDotPathValue(source, path);
|
||||
|
||||
if(value != Values[i]){
|
||||
//Changed
|
||||
if (changes == null) { changes = new List<BindingChange>(); }
|
||||
changes.Add(new BindingChange()
|
||||
{
|
||||
Path = PathStrings[i],
|
||||
PreviousValue = Values[i],
|
||||
Value = value
|
||||
});
|
||||
Values[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if(changes != null)
|
||||
{
|
||||
Callback(changes.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DotPathBinding : Binding
|
||||
{
|
||||
private PropertyInfo[] Path;
|
||||
private object LastValue;
|
||||
private Func<object, object> Converter;
|
||||
|
||||
public DotPathBinding(object target, string targetProperty, PropertyInfo[] path, Func<object, object> converter) : this(target, targetProperty, path)
|
||||
{
|
||||
this.Converter = converter;
|
||||
}
|
||||
|
||||
public DotPathBinding(object target, string targetProperty, PropertyInfo[] path) : base(target, targetProperty)
|
||||
{
|
||||
this.Path = path;
|
||||
}
|
||||
|
||||
public override void Digest(object source){
|
||||
var value = GetValue(source);
|
||||
if(value != LastValue){
|
||||
LastValue = value;
|
||||
if (Converter != null)
|
||||
{
|
||||
SetValue(Converter(value));
|
||||
}
|
||||
else
|
||||
{
|
||||
SetValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object GetValue(object source)
|
||||
{
|
||||
return DotPath.GetDotPathValue(source, Path);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class Binding : IBinding
|
||||
{
|
||||
private object Target;
|
||||
private string TargetProperty;
|
||||
private PropertyInfo[] TargetPropertyPath;
|
||||
|
||||
public Binding(object target, string targetProperty)
|
||||
{
|
||||
Target = target;
|
||||
TargetProperty = targetProperty;
|
||||
TargetPropertyPath = DotPath.CompileDotPath(target.GetType(), targetProperty);
|
||||
}
|
||||
|
||||
protected void SetValue(object value)
|
||||
{
|
||||
DotPath.SetDotPathValue(Target, TargetPropertyPath, value);
|
||||
}
|
||||
|
||||
public abstract void Digest(object source);
|
||||
|
||||
public virtual void Dispose() { Target = null; }
|
||||
|
||||
public static List<IDisposable> All = new List<IDisposable>();
|
||||
public static void DisposeAll()
|
||||
{
|
||||
//dispose of all bindings that are left over (city leave return)
|
||||
lock (All)
|
||||
{
|
||||
foreach (var item in All)
|
||||
{
|
||||
item.Dispose();
|
||||
}
|
||||
All.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface IBinding
|
||||
{
|
||||
void Digest(object source);
|
||||
|
||||
//object GetValue(object source);
|
||||
//void SetValue(object value);
|
||||
}
|
||||
}
|
58
server/tso.common/Utils/Cache/CacheKey.cs
Executable file
58
server/tso.common/Utils/Cache/CacheKey.cs
Executable file
|
@ -0,0 +1,58 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FSO.Common.Utils.Cache
|
||||
{
|
||||
public class CacheKey : IEqualityComparer<CacheKey>, IEquatable<CacheKey>
|
||||
{
|
||||
public static CacheKey Root = CacheKey.For();
|
||||
|
||||
public string[] Components { get; internal set; }
|
||||
|
||||
protected CacheKey(string[] components)
|
||||
{
|
||||
this.Components = components;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return GetHashCode(this);
|
||||
}
|
||||
|
||||
public bool Equals(CacheKey x, CacheKey y)
|
||||
{
|
||||
return x.Components.SequenceEqual(y.Components);
|
||||
}
|
||||
|
||||
public bool Equals(CacheKey other)
|
||||
{
|
||||
return Equals(this, other);
|
||||
}
|
||||
|
||||
public int GetHashCode(CacheKey obj)
|
||||
{
|
||||
int hashcode = 0;
|
||||
foreach (string value in obj.Components)
|
||||
{
|
||||
if (value != null)
|
||||
hashcode += value.GetHashCode();
|
||||
}
|
||||
return hashcode;
|
||||
}
|
||||
|
||||
public static CacheKey For(params object[] components)
|
||||
{
|
||||
return new CacheKey(components.Select(x => "" + x).ToArray());
|
||||
}
|
||||
|
||||
public static CacheKey Combine(CacheKey domain, params object[] components)
|
||||
{
|
||||
var newComponents = new string[domain.Components.Length+components.Length];
|
||||
Array.Copy(domain.Components, newComponents, domain.Components.Length);
|
||||
Array.Copy(components.Select(x => "" + x).ToArray(), 0, newComponents, domain.Components.Length, components.Length);
|
||||
return new CacheKey(newComponents);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
340
server/tso.common/Utils/Cache/FileSystemCache.cs
Executable file
340
server/tso.common/Utils/Cache/FileSystemCache.cs
Executable file
|
@ -0,0 +1,340 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FSO.Common.Utils.Cache
|
||||
{
|
||||
public class FileSystemCache : ICache
|
||||
{
|
||||
private const int DIGEST_DELAY = 10000;
|
||||
|
||||
private string _Directory { get; set; }
|
||||
private Queue<FileSystemCacheMutation> _Mutations;
|
||||
private Thread _DigestThread;
|
||||
private bool _Active;
|
||||
private long _CacheSize;
|
||||
private long _MaxCacheSize;
|
||||
private Thread _MainThread;
|
||||
|
||||
private LinkedList<FileSystemCacheEntry> _Cache;
|
||||
private Dictionary<CacheKey, FileSystemCacheEntry> _Index;
|
||||
|
||||
|
||||
public FileSystemCache(string directory, long maxSize)
|
||||
{
|
||||
_MainThread = Thread.CurrentThread;
|
||||
_Directory = directory;
|
||||
_Mutations = new Queue<FileSystemCacheMutation>();
|
||||
_CacheSize = 0;
|
||||
_MaxCacheSize = maxSize;
|
||||
_Cache = new LinkedList<FileSystemCacheEntry>();
|
||||
_Index = new Dictionary<CacheKey, FileSystemCacheEntry>();
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
var tempList = new List<FileSystemCacheEntry>();
|
||||
_ScanDirectory(CacheKey.Root, tempList);
|
||||
|
||||
foreach(var item in tempList.OrderByDescending(x => x.LastRead)){
|
||||
_Cache.AddLast(item);
|
||||
_Index.Add(item.Key, item);
|
||||
_CacheSize += item.Length;
|
||||
}
|
||||
|
||||
_Active = true;
|
||||
_DigestThread = new Thread(DigestLoop);
|
||||
_DigestThread.Priority = ThreadPriority.BelowNormal;
|
||||
_DigestThread.Start();
|
||||
}
|
||||
|
||||
|
||||
private void DigestLoop()
|
||||
{
|
||||
while (_Active && _MainThread?.IsAlive != false)
|
||||
{
|
||||
Digest();
|
||||
Thread.Sleep(DIGEST_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
private void Digest()
|
||||
{
|
||||
lock (_Mutations)
|
||||
{
|
||||
/**
|
||||
* We can avoid some work & disk hits by removing tasks for keys modified in later tasks
|
||||
*/
|
||||
List<FileSystemCacheMutation> tasks = new List<FileSystemCacheMutation>();
|
||||
Dictionary<CacheKey, FileSystemCacheMutation> taskIndex = new Dictionary<CacheKey, FileSystemCacheMutation>();
|
||||
FileSystemCacheMutation mutation;
|
||||
while (_Mutations.Count > 0 && (mutation = _Mutations.Dequeue()) != null)
|
||||
{
|
||||
tasks.Add(mutation);
|
||||
|
||||
FileSystemCacheMutation previousTask = null;
|
||||
taskIndex.TryGetValue(mutation.Key, out previousTask);
|
||||
if (previousTask == null) {
|
||||
taskIndex[mutation.Key] = mutation;
|
||||
continue;
|
||||
}
|
||||
|
||||
tasks.Remove(previousTask);
|
||||
taskIndex[mutation.Key] = mutation;
|
||||
}
|
||||
|
||||
|
||||
int bytesRequired = 0;
|
||||
foreach(var task in tasks){
|
||||
bytesRequired += task.GetBytesRequired(this);
|
||||
}
|
||||
|
||||
if(bytesRequired > 0 && (_CacheSize + bytesRequired) >= _MaxCacheSize)
|
||||
{
|
||||
//We need to evict some entries to make room
|
||||
while((_CacheSize + bytesRequired) >= _MaxCacheSize && _Cache.Count > 0)
|
||||
{
|
||||
var last = _Cache.Last;
|
||||
try {
|
||||
new FileSystemCacheRemoveMutation() {
|
||||
Key = last.Value.Key
|
||||
}.Execute(this);
|
||||
}catch(Exception ex){
|
||||
}
|
||||
CalculateCacheSize();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var task in tasks)
|
||||
{
|
||||
try
|
||||
{
|
||||
task.Execute(this);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
//Recalculate cache size if we made changes
|
||||
if(tasks.Count > 0){
|
||||
CalculateCacheSize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateCacheSize()
|
||||
{
|
||||
long size = 0;
|
||||
foreach(var item in _Cache){
|
||||
size += item.Length;
|
||||
}
|
||||
_CacheSize = size;
|
||||
}
|
||||
|
||||
private void _ScanDirectory(CacheKey parent, List<FileSystemCacheEntry> tempList)
|
||||
{
|
||||
var dir = GetFilePath(parent);
|
||||
if (!Directory.Exists(dir)) { return; }
|
||||
|
||||
var info = new DirectoryInfo(dir);
|
||||
|
||||
foreach(FileInfo file in info.GetFiles())
|
||||
{
|
||||
var key = CacheKey.Combine(parent, file.Name);
|
||||
tempList.Add(new FileSystemCacheEntry {
|
||||
Key = key,
|
||||
LastRead = file.LastAccessTime,
|
||||
LastWrite = file.LastWriteTime,
|
||||
Length = file.Length
|
||||
});
|
||||
}
|
||||
|
||||
foreach(var subDir in info.GetDirectories())
|
||||
{
|
||||
var key = CacheKey.Combine(parent, subDir.Name);
|
||||
_ScanDirectory(key, tempList);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsKey(CacheKey key)
|
||||
{
|
||||
return _Index.ContainsKey(key);
|
||||
}
|
||||
|
||||
public void Add(CacheKey key, byte[] bytes)
|
||||
{
|
||||
var clone = new byte[bytes.Length];
|
||||
Buffer.BlockCopy(bytes, 0, clone, 0, bytes.Length);
|
||||
|
||||
lock (_Mutations)
|
||||
{
|
||||
_Mutations.Enqueue(new FileSystemCacheAddMutation
|
||||
{
|
||||
Key = key,
|
||||
Data = clone
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(CacheKey key)
|
||||
{
|
||||
lock (_Mutations)
|
||||
{
|
||||
_Mutations.Enqueue(new FileSystemCacheRemoveMutation
|
||||
{
|
||||
Key = key
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public Task<T> Get<T>(CacheKey key)
|
||||
{
|
||||
if(typeof(T) == typeof(byte[]))
|
||||
{
|
||||
return Task.Factory.StartNew(() =>
|
||||
{
|
||||
var file = GetFilePath(key);
|
||||
byte[] result = null;
|
||||
if (File.Exists(file)){
|
||||
result = File.ReadAllBytes(file);
|
||||
}else{
|
||||
throw new Exception("File not found");
|
||||
}
|
||||
TouchEntry(key);
|
||||
return (T)(object)result;
|
||||
});
|
||||
}
|
||||
|
||||
throw new Exception("Not implemented yet");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_Active = false;
|
||||
}
|
||||
|
||||
internal string GetFilePath(CacheKey key)
|
||||
{
|
||||
return Path.Combine(_Directory, Path.Combine(key.Components));
|
||||
}
|
||||
|
||||
internal FileSystemCacheEntry GetEntry(CacheKey key)
|
||||
{
|
||||
FileSystemCacheEntry entry = null;
|
||||
_Index.TryGetValue(key, out entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
internal void AddEntry(FileSystemCacheEntry entry)
|
||||
{
|
||||
var existing = GetEntry(entry.Key);
|
||||
if(existing != null){
|
||||
_Cache.Remove(existing);
|
||||
}
|
||||
|
||||
_Cache.AddLast(entry);
|
||||
_Index[entry.Key] = entry;
|
||||
}
|
||||
|
||||
internal void RemoveEntry(CacheKey key)
|
||||
{
|
||||
var existing = GetEntry(key);
|
||||
if (existing != null)
|
||||
{
|
||||
_Cache.Remove(existing);
|
||||
_Index.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
internal void TouchEntry(CacheKey key)
|
||||
{
|
||||
var existing = GetEntry(key);
|
||||
if (existing != null)
|
||||
{
|
||||
_Cache.AddFirst(existing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface FileSystemCacheMutation
|
||||
{
|
||||
CacheKey Key { get; }
|
||||
|
||||
int GetBytesRequired(FileSystemCache cache);
|
||||
void Execute(FileSystemCache cache);
|
||||
}
|
||||
|
||||
public class FileSystemCacheAddMutation : FileSystemCacheMutation
|
||||
{
|
||||
public CacheKey Key { get; set; }
|
||||
public byte[] Data { get; set; }
|
||||
|
||||
public void Execute(FileSystemCache cache)
|
||||
{
|
||||
var path = cache.GetFilePath(Key);
|
||||
var finalPart = path.LastIndexOf('/');
|
||||
Directory.CreateDirectory((finalPart == -1)?path:path.Substring(0, finalPart));
|
||||
File.WriteAllBytes(path, Data);
|
||||
|
||||
var entry = cache.GetEntry(Key);
|
||||
if (entry == null)
|
||||
{
|
||||
entry = new FileSystemCacheEntry();
|
||||
entry.Key = Key;
|
||||
entry.LastRead = DateTime.MinValue;
|
||||
entry.LastWrite = DateTime.Now;
|
||||
entry.Length = Data.Length;
|
||||
|
||||
cache.AddEntry(entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
entry.Length = Data.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetBytesRequired(FileSystemCache cache)
|
||||
{
|
||||
var existingFile = cache.GetEntry(Key);
|
||||
if(existingFile != null)
|
||||
{
|
||||
return Data.Length - (int)existingFile.Length;
|
||||
}
|
||||
|
||||
return Data.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public class FileSystemCacheRemoveMutation : FileSystemCacheMutation
|
||||
{
|
||||
public CacheKey Key { get; set; }
|
||||
|
||||
public void Execute(FileSystemCache cache)
|
||||
{
|
||||
File.Delete(cache.GetFilePath(Key));
|
||||
cache.RemoveEntry(Key);
|
||||
}
|
||||
|
||||
public int GetBytesRequired(FileSystemCache cache)
|
||||
{
|
||||
var existingFile = cache.GetEntry(Key);
|
||||
if (existingFile != null)
|
||||
{
|
||||
return -((int)existingFile.Length);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public class FileSystemCacheEntry
|
||||
{
|
||||
public CacheKey Key;
|
||||
public DateTime LastWrite;
|
||||
public DateTime LastRead;
|
||||
public long Length;
|
||||
}
|
||||
}
|
20
server/tso.common/Utils/Cache/ICache.cs
Executable file
20
server/tso.common/Utils/Cache/ICache.cs
Executable file
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FSO.Common.Utils.Cache
|
||||
{
|
||||
public interface ICache : IDisposable
|
||||
{
|
||||
bool ContainsKey(CacheKey key);
|
||||
void Add(CacheKey key, byte[] bytes);
|
||||
void Remove(CacheKey key);
|
||||
|
||||
Task<T> Get<T>(CacheKey key);
|
||||
|
||||
//bool IsReady { get; }
|
||||
//bool Contains(string type, string key);
|
||||
//Task<byte[]> GetBytes(string type, string key);
|
||||
//Task PutBytes(string type, string key, byte[] bytes);
|
||||
//Task Init();
|
||||
}
|
||||
}
|
8
server/tso.common/Utils/Callback.cs
Executable file
8
server/tso.common/Utils/Callback.cs
Executable file
|
@ -0,0 +1,8 @@
|
|||
namespace FSO.Common.Utils
|
||||
{
|
||||
|
||||
public delegate void Callback();
|
||||
public delegate void Callback<T>(T data);
|
||||
public delegate void Callback<T, T2>(T data, T2 data2);
|
||||
public delegate void Callback<T, T2, T3>(T data, T2 data2, T3 data3);
|
||||
}
|
51
server/tso.common/Utils/CollectionUtils.cs
Executable file
51
server/tso.common/Utils/CollectionUtils.cs
Executable file
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FSO.Client.Utils
|
||||
{
|
||||
public static class CollectionUtils
|
||||
{
|
||||
public static void Shuffle<T>(this IList<T> list)
|
||||
{
|
||||
Random rng = new Random();
|
||||
int n = list.Count;
|
||||
while (n > 1)
|
||||
{
|
||||
n--;
|
||||
int k = rng.Next(n + 1);
|
||||
T value = list[k];
|
||||
list[k] = list[n];
|
||||
list[n] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<TResult> Select<TSource, TResult>(this Array items, Func<TSource, TResult> converter)
|
||||
{
|
||||
var result = new List<TResult>();
|
||||
foreach (var item in items)
|
||||
{
|
||||
result.Add(converter((TSource)item));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public static Dictionary<TKey, TValue> Clone<TKey, TValue>(Dictionary<TKey, TValue> input)
|
||||
{
|
||||
var result = new Dictionary<TKey, TValue>();
|
||||
foreach (var val in input)
|
||||
{
|
||||
result.Add(val.Key, val.Value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private static Random RAND = new Random();
|
||||
public static T RandomItem<T>(this T[] items)
|
||||
{
|
||||
var index = RAND.Next(items.Length);
|
||||
return items[index];
|
||||
}
|
||||
}
|
||||
}
|
110
server/tso.common/Utils/CurLoader.cs
Executable file
110
server/tso.common/Utils/CurLoader.cs
Executable file
|
@ -0,0 +1,110 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
public static class CurLoader
|
||||
{
|
||||
|
||||
public static MouseCursor LoadMonoCursor(GraphicsDevice gd, Stream stream)
|
||||
{
|
||||
var cur = LoadCursor(gd, stream);
|
||||
return MouseCursor.FromTexture2D(cur.Item1, cur.Item2.X, cur.Item2.Y);
|
||||
}
|
||||
|
||||
public static MouseCursor[] LoadUpgradeCursors(GraphicsDevice gd, Stream stream, int maxStars)
|
||||
{
|
||||
var cur = LoadCursor(gd, stream);
|
||||
Texture2D starTex;
|
||||
using (var str = File.Open("Content/uigraphics/upgrade_star.png", FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
starTex = Texture2D.FromStream(gd, str);
|
||||
}
|
||||
|
||||
var batch = new SpriteBatch(gd);
|
||||
var results = new MouseCursor[maxStars];
|
||||
for (int i = 0; i < maxStars; i++) {
|
||||
var starPos = cur.Item1.Width - 12;
|
||||
var width = Math.Max(starPos + 8 * i + 9, cur.Item1.Width);
|
||||
var tex = new RenderTarget2D(gd, width, Math.Max(width, cur.Item1.Height));
|
||||
gd.SetRenderTarget(tex);
|
||||
gd.Clear(Color.Transparent);
|
||||
batch.Begin(SpriteSortMode.Immediate);
|
||||
batch.Draw(cur.Item1, Vector2.Zero, Color.White);
|
||||
for (int j=0; j<=i; j++)
|
||||
{
|
||||
batch.Draw(starTex, new Vector2(starPos, 5), Color.White);
|
||||
starPos += 8;
|
||||
}
|
||||
batch.End();
|
||||
gd.SetRenderTarget(null);
|
||||
results[i] = MouseCursor.FromTexture2D(tex, cur.Item2.X, cur.Item2.Y);
|
||||
}
|
||||
starTex.Dispose();
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public static Func<GraphicsDevice, Stream, Texture2D> BmpLoaderFunc;
|
||||
|
||||
public static Tuple<Texture2D, Point> LoadCursor(GraphicsDevice gd, Stream stream)
|
||||
{
|
||||
using (var io = new BinaryReader(stream))
|
||||
{
|
||||
//little endian
|
||||
var tempbmp = new MemoryStream();
|
||||
var outIO = new BinaryWriter(tempbmp);
|
||||
|
||||
var reserved = io.ReadInt16();
|
||||
var type = io.ReadInt16();
|
||||
if (type != 2) throw new Exception("Not a cursor!");
|
||||
var images = io.ReadInt16(); //currently discard extra images...
|
||||
|
||||
//read first image
|
||||
var width = io.ReadByte();
|
||||
var height = io.ReadByte();
|
||||
var colors = io.ReadByte();
|
||||
var reserved2 = io.ReadByte();
|
||||
var xOffset = io.ReadInt16();
|
||||
var yOffset = io.ReadInt16();
|
||||
var size = io.ReadInt32();
|
||||
var offset = io.ReadInt32();
|
||||
stream.Seek(offset - 22, SeekOrigin.Current);
|
||||
|
||||
//ok... now write the bitmap data to a fake bitmap
|
||||
outIO.Write(new char[] { 'B', 'M' });
|
||||
outIO.Write(size + 14); //size, plus header
|
||||
outIO.Write(0);
|
||||
outIO.Write(14);
|
||||
var data = new byte[size];
|
||||
stream.Read(data, 0, size);
|
||||
outIO.Write(data);
|
||||
|
||||
tempbmp.Seek(0, SeekOrigin.Begin);
|
||||
var tex = BmpLoaderFunc(gd, tempbmp);
|
||||
|
||||
//our mask is on top. the image is on bottom.
|
||||
var odata = new byte[tex.Width * tex.Height * 4];
|
||||
tex.GetData(odata);
|
||||
var ndata = new byte[tex.Width * tex.Height * 2];
|
||||
var limit = ndata.Length;
|
||||
for (int i=0; i< limit; i+=4)
|
||||
{
|
||||
var j = i + limit;
|
||||
ndata[i] = (byte)((~odata[i]) & odata[j]);
|
||||
ndata[i+1] = ndata[i];
|
||||
ndata[i+2] = ndata[i];
|
||||
ndata[i+3] = (byte)(~odata[i]);
|
||||
}
|
||||
var oTex = new Texture2D(gd, width, height);
|
||||
oTex.SetData(ndata);
|
||||
tex.Dispose();
|
||||
|
||||
return new Tuple<Texture2D, Point>(oTex, new Point(xOffset, yOffset));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
server/tso.common/Utils/DebugUtils.cs
Executable file
12
server/tso.common/Utils/DebugUtils.cs
Executable file
|
@ -0,0 +1,12 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
public static class DebugUtils
|
||||
{
|
||||
public static string LogObject(object obj)
|
||||
{
|
||||
return JsonConvert.SerializeObject(obj);
|
||||
}
|
||||
}
|
||||
}
|
75
server/tso.common/Utils/DirectionUtils.cs
Executable file
75
server/tso.common/Utils/DirectionUtils.cs
Executable file
|
@ -0,0 +1,75 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
public static class DirectionUtils
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Finds the difference between two radian directions.
|
||||
/// </summary>
|
||||
/// <param name="a">The direction to subtract from.</param>
|
||||
/// <param name="b">The direction to subtract.</param>
|
||||
public static double Difference(double a, double b) {
|
||||
double value = PosMod(b-a, Math.PI*2);
|
||||
if (value > Math.PI) value -= Math.PI * 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes a direction to the range -PI through PI.
|
||||
/// </summary>
|
||||
/// <param name="dir">The direction to normalize.</param>
|
||||
public static double Normalize(double dir)
|
||||
{
|
||||
dir = PosMod(dir, Math.PI * 2);
|
||||
if (dir > Math.PI) dir -= Math.PI * 2;
|
||||
return dir;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes a direction in degrees to the range -180 through 180.
|
||||
/// </summary>
|
||||
/// <param name="dir">The direction to normalize.</param>
|
||||
public static double NormalizeDegrees(double dir)
|
||||
{
|
||||
dir = PosMod(dir, 360);
|
||||
if (dir > 180) dir -= 360;
|
||||
return dir;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the mathematical modulus of a value.
|
||||
/// </summary>
|
||||
/// <param name="x">The number to mod.</param>
|
||||
/// <param name="x">The factor to use.</param>
|
||||
public static double PosMod(double x, double m)
|
||||
{
|
||||
return (x % m + m) % m;
|
||||
}
|
||||
|
||||
private static int[] tab32 = new int[] {
|
||||
0, 9, 1, 10, 13, 21, 2, 29,
|
||||
11, 14, 16, 18, 22, 25, 3, 30,
|
||||
8, 12, 20, 28, 15, 17, 24, 7,
|
||||
19, 27, 23, 6, 26, 5, 4, 31
|
||||
};
|
||||
|
||||
public static int Log2Int(uint value)
|
||||
{
|
||||
value |= value >> 1;
|
||||
value |= value >> 2;
|
||||
value |= value >> 4;
|
||||
value |= value >> 8;
|
||||
value |= value >> 16;
|
||||
return tab32[(uint)(value * 0x07C4ACDD) >> 27];
|
||||
}
|
||||
|
||||
public static float QuaternionDistance(Quaternion q1, Quaternion q2)
|
||||
{
|
||||
var inner = q1.X * q2.X + q1.Y * q2.Y + q1.Z * q2.Z + q1.W * q2.W;
|
||||
return (float)Math.Acos(2 * (inner * inner) - 1);
|
||||
}
|
||||
}
|
||||
}
|
54
server/tso.common/Utils/DotPath.cs
Executable file
54
server/tso.common/Utils/DotPath.cs
Executable file
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
public static class DotPath
|
||||
{
|
||||
public static PropertyInfo[] CompileDotPath(Type sourceType, string sourcePath)
|
||||
{
|
||||
//Dot path
|
||||
var path = sourcePath.Split(new char[] { '.' });
|
||||
var properties = new PropertyInfo[path.Length];
|
||||
|
||||
var currentType = sourceType;
|
||||
for (int i = 0; i < path.Length; i++)
|
||||
{
|
||||
var property = currentType.GetProperty(path[i]);
|
||||
properties[i] = property;
|
||||
currentType = property.PropertyType;
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
public static object GetDotPathValue(object source, PropertyInfo[] path)
|
||||
{
|
||||
if (source == null) { return null; }
|
||||
|
||||
var currentValue = source;
|
||||
for (var i = 0; i < path.Length; i++)
|
||||
{
|
||||
currentValue = path[i].GetValue(currentValue, null);
|
||||
if (currentValue == null) { return null; }
|
||||
}
|
||||
|
||||
return currentValue;
|
||||
}
|
||||
|
||||
public static void SetDotPathValue(object source, PropertyInfo[] path, object value)
|
||||
{
|
||||
if (source == null) { return; }
|
||||
|
||||
var currentValue = source;
|
||||
for (var i = 0; i < path.Length - 1; i++)
|
||||
{
|
||||
currentValue = path[i].GetValue(currentValue, null);
|
||||
if (currentValue == null) { return; }
|
||||
}
|
||||
|
||||
var member = path[path.Length - 1];
|
||||
member.SetValue(currentValue, value, null);
|
||||
}
|
||||
}
|
||||
}
|
68
server/tso.common/Utils/FeatureLevelTest.cs
Executable file
68
server/tso.common/Utils/FeatureLevelTest.cs
Executable file
|
@ -0,0 +1,68 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
public class FeatureLevelTest
|
||||
{
|
||||
public static bool UpdateFeatureLevel(GraphicsDevice gd)
|
||||
{
|
||||
//if 3d is enabled, check if we support non-power-of-two mipmaps
|
||||
if (FSOEnvironment.SoftwareKeyboard && FSOEnvironment.SoftwareDepth)
|
||||
{
|
||||
FSOEnvironment.EnableNPOTMip = false;
|
||||
return true;
|
||||
}
|
||||
try
|
||||
{
|
||||
using (var mipTest = new Texture2D(gd, 11, 11, true, SurfaceFormat.Color))
|
||||
{
|
||||
var data = new Color[11 * 11];
|
||||
TextureUtils.UploadWithMips(mipTest, gd, data);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
FSOEnvironment.EnableNPOTMip = false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (var mipTest = new Texture2D(gd, 4, 4, true, SurfaceFormat.Dxt5))
|
||||
{
|
||||
var data = new byte[16];
|
||||
mipTest.SetData(data);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
FSOEnvironment.TexCompressSupport = false;
|
||||
}
|
||||
|
||||
//msaa test
|
||||
try
|
||||
{
|
||||
var msaaTarg = new RenderTarget2D(gd, 1, 1, false, SurfaceFormat.Color, DepthFormat.None, 8, RenderTargetUsage.PreserveContents);
|
||||
gd.SetRenderTarget(msaaTarg);
|
||||
gd.Clear(Color.Red);
|
||||
|
||||
var tex = TextureUtils.CopyAccelerated(gd, msaaTarg);
|
||||
|
||||
var result = new Color[1];
|
||||
tex.GetData(result);
|
||||
FSOEnvironment.MSAASupport = result[0] == Color.Red;
|
||||
gd.SetRenderTarget(null);
|
||||
msaaTarg.Dispose();
|
||||
tex.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
gd.SetRenderTarget(null);
|
||||
FSOEnvironment.MSAASupport = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
25
server/tso.common/Utils/FileUtils.cs
Executable file
25
server/tso.common/Utils/FileUtils.cs
Executable file
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
public class FileUtils
|
||||
{
|
||||
public static string ComputeMD5(string filePath){
|
||||
var bytes = ComputeMD5Bytes(filePath);
|
||||
return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
|
||||
public static byte[] ComputeMD5Bytes(string filePath)
|
||||
{
|
||||
using (var md5 = MD5.Create())
|
||||
{
|
||||
using (var stream = File.OpenRead(filePath))
|
||||
{
|
||||
return md5.ComputeHash(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
274
server/tso.common/Utils/GameThread.cs
Executable file
274
server/tso.common/Utils/GameThread.cs
Executable file
|
@ -0,0 +1,274 @@
|
|||
using FSO.Common.Rendering.Framework.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
public class GameThreadInterval
|
||||
{
|
||||
private Callback Callback;
|
||||
private long Interval;
|
||||
private double EndTime = -1;
|
||||
|
||||
private UpdateHook _TickHook;
|
||||
private bool _Clear;
|
||||
|
||||
public GameThreadInterval(Callback callback, long interval)
|
||||
{
|
||||
Callback = callback;
|
||||
Interval = interval;
|
||||
_TickHook = GameThread.EveryUpdate(Tick);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_Clear = true;
|
||||
}
|
||||
|
||||
private void Tick(UpdateState state)
|
||||
{
|
||||
if (_Clear)
|
||||
{
|
||||
_TickHook.Remove();
|
||||
return;
|
||||
}
|
||||
|
||||
var now = state.Time.TotalGameTime.TotalMilliseconds;
|
||||
if (EndTime == -1)
|
||||
{
|
||||
EndTime = now + Interval;
|
||||
}
|
||||
|
||||
if (EndTime <= now)
|
||||
{
|
||||
Callback();
|
||||
EndTime = now + Interval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class GameThreadTimeout
|
||||
{
|
||||
private Callback Callback;
|
||||
private long Delay;
|
||||
private double EndTime = -1;
|
||||
|
||||
private UpdateHook _TickHook;
|
||||
private bool _Clear;
|
||||
|
||||
public GameThreadTimeout(Callback callback, long delay)
|
||||
{
|
||||
Callback = callback;
|
||||
Delay = delay;
|
||||
_TickHook = GameThread.EveryUpdate(Tick);
|
||||
}
|
||||
|
||||
public void Clear(){
|
||||
_Clear = true;
|
||||
}
|
||||
|
||||
private void Tick(UpdateState state)
|
||||
{
|
||||
if (_Clear){
|
||||
_TickHook.Remove();
|
||||
return;
|
||||
}
|
||||
|
||||
var now = state.Time.TotalGameTime.TotalMilliseconds;
|
||||
if (EndTime == -1){
|
||||
EndTime = now + Delay;
|
||||
}
|
||||
|
||||
if(EndTime <= now){
|
||||
_TickHook.Remove();
|
||||
Callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class UpdateHook
|
||||
{
|
||||
public bool RemoveNext = false;
|
||||
public Callback<UpdateState> Callback;
|
||||
|
||||
public void Remove(){
|
||||
RemoveNext = true;
|
||||
}
|
||||
}
|
||||
|
||||
public class GameThread
|
||||
{
|
||||
public static bool Killed;
|
||||
public static bool NoGame;
|
||||
public static EventWaitHandle OnKilled = new EventWaitHandle(false, EventResetMode.ManualReset);
|
||||
public static event Action KilledEvent;
|
||||
public static Thread Game;
|
||||
public static bool UpdateExecuting;
|
||||
private static List<UpdateHook> _UpdateHooks = new List<UpdateHook>();
|
||||
private static UpdateHook[] _UpdateHooksCopy = new UpdateHook[0];
|
||||
private static List<UpdateHook> _UpdateHooksRemove = new List<UpdateHook>();
|
||||
private static Queue<Callback<UpdateState>> _UpdateCallbacks = new Queue<Callback<UpdateState>>();
|
||||
private static Queue<Callback<UpdateState>> _UpdateCallbacksSwap = new Queue<Callback<UpdateState>>();
|
||||
public static AutoResetEvent OnWork = new AutoResetEvent(false);
|
||||
|
||||
public static void SetKilled()
|
||||
{
|
||||
Killed = true;
|
||||
OnKilled.Set();
|
||||
KilledEvent?.Invoke();
|
||||
}
|
||||
|
||||
public static GameThreadTimeout SetTimeout(Callback callback, long delay)
|
||||
{
|
||||
var result = new GameThreadTimeout(callback, delay);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static GameThreadInterval SetInterval(Callback callback, long delay)
|
||||
{
|
||||
var result = new GameThreadInterval(callback, delay);
|
||||
return result;
|
||||
}
|
||||
|
||||
//I know we already have a way to do this with IUIProcess but we need a way for other libs that dont
|
||||
//have the UI code for reference
|
||||
public static UpdateHook EveryUpdate(Callback<UpdateState> callback)
|
||||
{
|
||||
var newHook = new UpdateHook()
|
||||
{
|
||||
Callback = callback
|
||||
};
|
||||
|
||||
lock (_UpdateHooks)
|
||||
{
|
||||
_UpdateHooks.Add(newHook);
|
||||
}
|
||||
|
||||
return newHook;
|
||||
}
|
||||
|
||||
public static void NextUpdate(Callback<UpdateState> callback)
|
||||
{
|
||||
lock (_UpdateCallbacks)
|
||||
{
|
||||
_UpdateCallbacks.Enqueue(callback);
|
||||
}
|
||||
OnWork.Set();
|
||||
}
|
||||
|
||||
public static void InUpdate(Callback callback)
|
||||
{
|
||||
if (IsInGameThread() && UpdateExecuting){
|
||||
callback();
|
||||
}else{
|
||||
NextUpdate(x => callback());
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsInGameThread()
|
||||
{
|
||||
var thread = Thread.CurrentThread;
|
||||
if (thread == Game || NoGame)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Task<T> NextUpdate<T>(Func<UpdateState, T> callback)
|
||||
{
|
||||
TaskCompletionSource<T> task = new TaskCompletionSource<T>();
|
||||
lock (_UpdateCallbacks)
|
||||
{
|
||||
_UpdateCallbacks.Enqueue(x =>
|
||||
{
|
||||
task.SetResult(callback(x));
|
||||
});
|
||||
}
|
||||
return TimeoutAfter(task.Task, new TimeSpan(0, 0, 5));
|
||||
}
|
||||
|
||||
public static async Task<TResult> TimeoutAfter<TResult>(Task<TResult> task, TimeSpan timeout)
|
||||
{
|
||||
using (var timeoutCancellationTokenSource = new CancellationTokenSource())
|
||||
{
|
||||
|
||||
var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token));
|
||||
if (completedTask == task)
|
||||
{
|
||||
timeoutCancellationTokenSource.Cancel();
|
||||
return await task; // Very important in order to propagate exceptions
|
||||
}
|
||||
else
|
||||
{
|
||||
return default(TResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DigestUpdate(UpdateState state)
|
||||
{
|
||||
Queue<Callback<UpdateState>> _callbacks;
|
||||
|
||||
lock (_UpdateCallbacks)
|
||||
{
|
||||
// Swap the active callbacks queue with the second one, so we can
|
||||
// process entries without fear of more being added.
|
||||
|
||||
_callbacks = _UpdateCallbacks;
|
||||
_UpdateCallbacks = _UpdateCallbacksSwap;
|
||||
_UpdateCallbacksSwap = _callbacks;
|
||||
}
|
||||
|
||||
while (_callbacks.Count > 0)
|
||||
{
|
||||
_callbacks.Dequeue()(state);
|
||||
}
|
||||
|
||||
int hookCount;
|
||||
UpdateHook[] _hooks;
|
||||
List<UpdateHook> toRemove = _UpdateHooksRemove;
|
||||
|
||||
lock (_UpdateHooks)
|
||||
{
|
||||
hookCount = _UpdateHooks.Count;
|
||||
|
||||
if (_UpdateHooksCopy.Length < _UpdateHooks.Count)
|
||||
{
|
||||
_UpdateHooksCopy = _UpdateHooks.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
_UpdateHooks.CopyTo(_UpdateHooksCopy);
|
||||
}
|
||||
|
||||
_hooks = _UpdateHooksCopy;
|
||||
}
|
||||
|
||||
for (int i = 0; i < hookCount; i++)
|
||||
{
|
||||
var item = _hooks[i];
|
||||
item.Callback(state);
|
||||
if (item.RemoveNext)
|
||||
{
|
||||
toRemove.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove.Count > 0)
|
||||
{
|
||||
lock (_UpdateHooks)
|
||||
{
|
||||
foreach (var rem in toRemove) _UpdateHooks.Remove(rem);
|
||||
}
|
||||
|
||||
toRemove.Clear();
|
||||
}
|
||||
|
||||
//finally, check cache controller
|
||||
TimedReferenceController.Tick();
|
||||
}
|
||||
}
|
||||
}
|
348
server/tso.common/Utils/HTMLPrinter.cs
Executable file
348
server/tso.common/Utils/HTMLPrinter.cs
Executable file
|
@ -0,0 +1,348 @@
|
|||
//disabled for now so FSO.Files can reference this project.
|
||||
|
||||
/*
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using FSO.Files.Formats.IFF.Chunks;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// This tool helps export internal structures such as floor catalogs,
|
||||
/// wall catalogs etc to a html file to check that file parsing is working correctly
|
||||
/// </summary>
|
||||
public class HTMLPrinter : IDisposable
|
||||
{
|
||||
private List<object> sections = new List<object>();
|
||||
private Dictionary<string, string> patterns = new Dictionary<string, string>();
|
||||
private string dir;
|
||||
private string id;
|
||||
private GraphicsDevice Gd;
|
||||
|
||||
public HTMLPrinter(GraphicsDevice gd, string directory, string id)
|
||||
{
|
||||
this.Gd = gd;
|
||||
this.dir = directory;
|
||||
this.id = id;
|
||||
|
||||
sections.Add("<link rel=\"stylesheet\" type=\"text/css\" href=\"main.css\"></link>");
|
||||
|
||||
//Add the default patterns
|
||||
patterns.Add("h1", "<h1>{0}</h1>");
|
||||
patterns.Add("h2", "<h2>{0}</h2>");
|
||||
patterns.Add("h3", "<h3>{0}</h3>");
|
||||
}
|
||||
|
||||
public void H1(string text){
|
||||
Print("h1", text);
|
||||
}
|
||||
|
||||
public void H2(string text){
|
||||
Print("h2", text);
|
||||
}
|
||||
|
||||
public void H3(string text){
|
||||
Print("h3", text);
|
||||
}
|
||||
|
||||
public Table CreateTable()
|
||||
{
|
||||
return new Table();
|
||||
}
|
||||
|
||||
public void Add(object item){
|
||||
this.sections.Add(item);
|
||||
}
|
||||
|
||||
public Table CreateTable(params string[] columns)
|
||||
{
|
||||
var t = new Table();
|
||||
foreach (var col in columns)
|
||||
{
|
||||
t.WithColumn(new TableColumn(col));
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
public DataTable<T> AddDataTable<T>(IEnumerable<T> values)
|
||||
{
|
||||
var table = CreateDataTable<T>(values);
|
||||
sections.Add(table);
|
||||
return table;
|
||||
}
|
||||
|
||||
public DataTable<T> CreateDataTable<T>(IEnumerable<T> values)
|
||||
{
|
||||
return new DataTable<T>(values);
|
||||
}
|
||||
|
||||
private string ObjectToString(object obj)
|
||||
{
|
||||
if (obj != null)
|
||||
{
|
||||
return obj.ToString();
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
|
||||
public void Print(string pattern, params object[] args)
|
||||
{
|
||||
var value = string.Format(patterns[pattern], args);
|
||||
sections.Add(value);
|
||||
}
|
||||
|
||||
#region IDisposable Members
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Directory.CreateDirectory(Path.Combine(dir, id + "_files"));
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
foreach (var item in sections)
|
||||
{
|
||||
AppendItem(sb, item);
|
||||
}
|
||||
|
||||
File.WriteAllText(Path.Combine(dir, id + ".html"), sb.ToString());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
public void AppendItem(StringBuilder sb, object item)
|
||||
{
|
||||
if (item is IHTMLAppender)
|
||||
{
|
||||
((IHTMLAppender)item).Append(this, sb);
|
||||
}else if(item is SPR2Frame){
|
||||
|
||||
try
|
||||
{
|
||||
var path = ExportSpriteFrame((SPR2Frame)item);
|
||||
sb.Append("<img src='" + path + "'></img>");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
sb.Append("Failed to export");
|
||||
}
|
||||
}
|
||||
else if (item is SPRFrame)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = ExportSpriteFrame((SPRFrame)item);
|
||||
sb.Append("<img src='" + path + "'></img>");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
sb.Append("Failed to export");
|
||||
}
|
||||
}
|
||||
else if (item != null)
|
||||
{
|
||||
sb.Append(item.ToString());
|
||||
}
|
||||
|
||||
/**else if(item is SPR){
|
||||
|
||||
var t = CreateTable()
|
||||
.WithColumn(new TableColumn("Sprite", 2));
|
||||
|
||||
var sprP = (SPR)item;
|
||||
for (var i = 0; i < sprP.FrameCount; i++){
|
||||
try
|
||||
{
|
||||
var frame = sprP.GetFrame(i);
|
||||
t.AddRow((i + 1), frame);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
t.AddRow((i + 1), "Failed to export frame");
|
||||
}
|
||||
}
|
||||
|
||||
AppendItem(sb, t);
|
||||
|
||||
}**/
|
||||
|
||||
/**
|
||||
}
|
||||
|
||||
private string ExportSpriteFrame(SPRFrame frame)
|
||||
{
|
||||
var texture = frame.GetTexture(this.Gd);
|
||||
|
||||
var temp = Path.GetTempFileName();
|
||||
texture.SaveAsPng(new FileStream(temp, FileMode.OpenOrCreate), texture.Width, texture.Height);
|
||||
|
||||
var hash = FileUtils.ComputeMD5(temp);
|
||||
var filename = id + "_files/" + hash + ".png";
|
||||
var newDest = Path.Combine(dir, filename);
|
||||
if (File.Exists(newDest))
|
||||
{
|
||||
File.Delete(temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
File.Move(temp, newDest);
|
||||
}
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
private string ExportSpriteFrame(SPR2Frame frame)
|
||||
{
|
||||
var texture = frame.GetTexture(this.Gd);
|
||||
|
||||
var temp = Path.GetTempFileName();
|
||||
texture.SaveAsPng(new FileStream(temp, FileMode.OpenOrCreate), texture.Width, texture.Height);
|
||||
|
||||
var hash = FileUtils.ComputeMD5(temp);
|
||||
var filename = id + "_files/" + hash + ".png";
|
||||
var newDest = Path.Combine(dir, filename);
|
||||
if (File.Exists(newDest))
|
||||
{
|
||||
File.Delete(temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
File.Move(temp, newDest);
|
||||
}
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public class TableColumn
|
||||
{
|
||||
public TableColumn(string header)
|
||||
{
|
||||
this.Header = header;
|
||||
}
|
||||
public TableColumn(string header, int span)
|
||||
{
|
||||
this.Header = header;
|
||||
this.Span = span;
|
||||
}
|
||||
|
||||
public string Header;
|
||||
public int Span;
|
||||
}
|
||||
|
||||
public class Table : IHTMLAppender
|
||||
{
|
||||
private List<TableColumn> Columns = new List<TableColumn>();
|
||||
private List<object[]> Rows = new List<object[]>();
|
||||
|
||||
public Table WithColumn(TableColumn col)
|
||||
{
|
||||
Columns.Add(col);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Table AddRow(params object[] values)
|
||||
{
|
||||
Rows.Add(values);
|
||||
return this;
|
||||
}
|
||||
|
||||
#region IHTMLAppender Members
|
||||
|
||||
public void Append(HTMLPrinter printer, StringBuilder sb)
|
||||
{
|
||||
sb.Append("<table border='1'>");
|
||||
|
||||
if (Columns.Count > 0)
|
||||
{
|
||||
sb.Append("<tr>");
|
||||
foreach (var col in Columns)
|
||||
{
|
||||
sb.Append("<th colspan='" + col.Span + "'>" + col.Header + "</th>");
|
||||
}
|
||||
sb.Append("</tr>");
|
||||
}
|
||||
|
||||
foreach (var item in Rows)
|
||||
{
|
||||
sb.Append("<tr>");
|
||||
foreach(var col in item){
|
||||
sb.Append("<td>");
|
||||
printer.AppendItem(sb, col);
|
||||
sb.Append("</td>");
|
||||
}
|
||||
sb.Append("</tr>");
|
||||
}
|
||||
sb.Append("</table>");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
public class DataTable<T> : IHTMLAppender
|
||||
{
|
||||
private IEnumerable<T> Items;
|
||||
private List<DataTableColumn<T>> Columns;
|
||||
|
||||
public DataTable(IEnumerable<T> items)
|
||||
{
|
||||
this.Items = items;
|
||||
this.Columns = new List<DataTableColumn<T>>();
|
||||
}
|
||||
|
||||
|
||||
public DataTable<T> WithColumn(string label, Func<T, object> value)
|
||||
{
|
||||
Columns.Add(new DataTableColumn<T> { Heading = label, Value = value });
|
||||
return this;
|
||||
}
|
||||
|
||||
#region IHTMLAppender Members
|
||||
|
||||
public void Append(HTMLPrinter printer, StringBuilder sb)
|
||||
{
|
||||
sb.Append("<table border='1'>");
|
||||
sb.Append("<tr>");
|
||||
foreach (var col in Columns)
|
||||
{
|
||||
sb.Append("<td>" + col.Heading + "</td>");
|
||||
}
|
||||
sb.Append("</tr>");
|
||||
|
||||
foreach (var item in Items)
|
||||
{
|
||||
sb.Append("<tr>");
|
||||
foreach (var col in Columns)
|
||||
{
|
||||
sb.Append("<th>");
|
||||
printer.AppendItem(sb, col.Value(item));
|
||||
sb.Append("</th>");
|
||||
}
|
||||
sb.Append("</tr>");
|
||||
}
|
||||
sb.Append("</table>");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public interface IHTMLAppender
|
||||
{
|
||||
void Append(HTMLPrinter printer, StringBuilder sb);
|
||||
}
|
||||
|
||||
public class DataTableColumn<T> {
|
||||
public string Heading;
|
||||
public Func<T, object> Value;
|
||||
}
|
||||
}
|
||||
*/
|
7
server/tso.common/Utils/ITimedCachable.cs
Executable file
7
server/tso.common/Utils/ITimedCachable.cs
Executable file
|
@ -0,0 +1,7 @@
|
|||
namespace FSO.Common.Utils
|
||||
{
|
||||
public interface ITimedCachable
|
||||
{
|
||||
void Rereferenced(bool save);
|
||||
}
|
||||
}
|
43
server/tso.common/Utils/IXMLEntity.cs
Executable file
43
server/tso.common/Utils/IXMLEntity.cs
Executable file
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
using System.Xml;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
public interface IXMLEntity
|
||||
{
|
||||
XmlElement Serialize(XmlDocument doc);
|
||||
void Parse(XmlElement element);
|
||||
}
|
||||
|
||||
public static class XMLUtils
|
||||
{
|
||||
public static T Parse<T>(string data) where T : IXMLEntity
|
||||
{
|
||||
var doc = new XmlDocument();
|
||||
doc.LoadXml(data);
|
||||
|
||||
T result = (T)Activator.CreateInstance(typeof(T));
|
||||
result.Parse((XmlElement)doc.FirstChild);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void AppendTextNode(this XmlElement e, string nodeName, string value)
|
||||
{
|
||||
var node = e.OwnerDocument.CreateElement(nodeName);
|
||||
node.AppendChild(e.OwnerDocument.CreateTextNode(value));
|
||||
e.AppendChild(node);
|
||||
}
|
||||
|
||||
public static string ReadTextNode(this XmlElement e, string nodeName)
|
||||
{
|
||||
foreach (XmlElement child in e.ChildNodes)
|
||||
{
|
||||
if (child.Name == nodeName && child.FirstChild != null)
|
||||
{
|
||||
return child.FirstChild?.Value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
106
server/tso.common/Utils/IffPrinter.cs
Executable file
106
server/tso.common/Utils/IffPrinter.cs
Executable file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using FSO.Files.Formats.IFF;
|
||||
using FSO.Files.Formats.IFF.Chunks;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
public class IffPrinter
|
||||
{
|
||||
private HTMLPrinter Printer;
|
||||
|
||||
public IffPrinter(HTMLPrinter printer){
|
||||
this.Printer = printer;
|
||||
}
|
||||
|
||||
public void PrintAll(IffFile iff){
|
||||
this.Print<SPR>(iff);
|
||||
this.Print<SPR2>(iff);
|
||||
this.Print<OBJD>(iff);
|
||||
}
|
||||
|
||||
public void Print<T>(IffFile iff){
|
||||
var type = typeof(T);
|
||||
var items = iff.List<T>();
|
||||
|
||||
if (items == null) { return; }
|
||||
|
||||
if (type == typeof(SPR2)){
|
||||
Printer.H1("SPR2");
|
||||
foreach (var item in items){
|
||||
PrintSPR2((SPR2)(object)item);
|
||||
}
|
||||
}
|
||||
else if (type == typeof(OBJD))
|
||||
{
|
||||
Printer.H1("OBJD");
|
||||
foreach (var item in items){
|
||||
PrintOBJD((OBJD)(object)item);
|
||||
}
|
||||
}else if (type == typeof(SPR))
|
||||
{
|
||||
Printer.H1("SPR");
|
||||
foreach (var item in items)
|
||||
{
|
||||
PrintSPR((SPR)(object)item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PrintSPR(SPR spr)
|
||||
{
|
||||
Printer.H2("#" + spr.ChunkID + " (" + spr.ChunkLabel + ")");
|
||||
var table = Printer.CreateTable(new string[] { "Index", "Pixel" });
|
||||
var frameIndex = 0;
|
||||
foreach (var frame in spr.Frames)
|
||||
{
|
||||
table.AddRow(new object[] { frameIndex, frame });
|
||||
frameIndex++;
|
||||
}
|
||||
Printer.Add(table);
|
||||
}
|
||||
|
||||
private void PrintOBJD(OBJD item){
|
||||
string[] fieldLabels = null;
|
||||
switch (item.Version)
|
||||
{
|
||||
case 142:
|
||||
fieldLabels = OBJD.VERSION_142_Fields;
|
||||
break;
|
||||
}
|
||||
|
||||
Printer.H1(item.ChunkID + " (" + item.ChunkLabel + ") GUID = " + item.GUID.ToString("x") + " Version = " + item.Version);
|
||||
var table = Printer.CreateTable(new string[] { "Field", "Value" });
|
||||
for (var i = 0; i < item.RawData.Length; i++)
|
||||
{
|
||||
if (fieldLabels != null && i < fieldLabels.Length)
|
||||
{
|
||||
table.AddRow(new object[] { i.ToString() + " (" + fieldLabels[i] + ")", item.RawData[i].ToString() });
|
||||
}
|
||||
else
|
||||
{
|
||||
table.AddRow(new object[] { i.ToString(), item.RawData[i].ToString() });
|
||||
}
|
||||
}
|
||||
Printer.Add(table);
|
||||
}
|
||||
|
||||
private void PrintSPR2(SPR2 spr)
|
||||
{
|
||||
Printer.H2("#" + spr.ChunkID + " (" + spr.ChunkLabel + ")");
|
||||
var table = Printer.CreateTable(new string[] { "Index", "Pixel" });
|
||||
var frameIndex = 0;
|
||||
foreach (var frame in spr.Frames){
|
||||
table.AddRow(new object[] { frameIndex, frame });
|
||||
frameIndex++;
|
||||
}
|
||||
Printer.Add(table);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
15
server/tso.common/Utils/LinqUtils.cs
Executable file
15
server/tso.common/Utils/LinqUtils.cs
Executable file
|
@ -0,0 +1,15 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
public static class LinqUtils
|
||||
{
|
||||
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> items, int maxItems)
|
||||
{
|
||||
return items.Select((item, index) => new { item, index })
|
||||
.GroupBy(x => x.index / maxItems)
|
||||
.Select(g => g.Select(x => x.item));
|
||||
}
|
||||
}
|
||||
}
|
17
server/tso.common/Utils/MoneyFormatter.cs
Executable file
17
server/tso.common/Utils/MoneyFormatter.cs
Executable file
|
@ -0,0 +1,17 @@
|
|||
using System.Globalization;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
public class MoneyFormatter
|
||||
{
|
||||
public static string Format(uint money)
|
||||
{
|
||||
var val = money.ToString("N", new CultureInfo("en-US"));
|
||||
var io = val.LastIndexOf(".");
|
||||
if(io != -1){
|
||||
val = val.Substring(0, io);
|
||||
}
|
||||
return "$" + val;
|
||||
}
|
||||
}
|
||||
}
|
213
server/tso.common/Utils/PPXDepthEngine.cs
Executable file
213
server/tso.common/Utils/PPXDepthEngine.cs
Executable file
|
@ -0,0 +1,213 @@
|
|||
using System;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
public class PPXDepthEngine
|
||||
{
|
||||
private static GraphicsDevice GD;
|
||||
private static RenderTarget2D BackbufferDepth;
|
||||
private static RenderTarget2D Backbuffer;
|
||||
private static SpriteBatch SB;
|
||||
public static int SSAA = 1;
|
||||
public static int MSAA = 0;
|
||||
|
||||
public static void InitGD(GraphicsDevice gd)
|
||||
{
|
||||
GD = gd;
|
||||
SB = new SpriteBatch(gd);
|
||||
}
|
||||
public static void InitScreenTargets()
|
||||
{
|
||||
if (GD == null) return;
|
||||
if (BackbufferDepth != null) BackbufferDepth.Dispose();
|
||||
BackbufferDepth = null;
|
||||
if (Backbuffer != null) Backbuffer.Dispose();
|
||||
var scale = 1;//FSOEnvironment.DPIScaleFactor;
|
||||
if (!FSOEnvironment.Enable3D)
|
||||
BackbufferDepth = CreateRenderTarget(GD, 1, MSAA, SurfaceFormat.Color, SSAA*GD.Viewport.Width/scale, SSAA * GD.Viewport.Height / scale, DepthFormat.None);
|
||||
Backbuffer = CreateRenderTarget(GD, 1, MSAA, SurfaceFormat.Color, SSAA * GD.Viewport.Width / scale, SSAA * GD.Viewport.Height / scale, DepthFormat.Depth24Stencil8);
|
||||
}
|
||||
|
||||
private static RenderTarget2D ActiveColor;
|
||||
private static RenderTarget2D ActiveDepth;
|
||||
private static int StencilValue;
|
||||
|
||||
public static void SetPPXTarget(RenderTarget2D color, RenderTarget2D depth, bool clear)
|
||||
{
|
||||
SetPPXTarget(color, depth, clear, Color.TransparentBlack);
|
||||
}
|
||||
|
||||
public static void SetPPXTarget(RenderTarget2D color, RenderTarget2D depth, bool clear, Color clearColor)
|
||||
{
|
||||
if (color == null && depth == null && Backbuffer != null) color = Backbuffer;
|
||||
ActiveColor = color;
|
||||
if (color == Backbuffer && depth == null && BackbufferDepth != null) depth = BackbufferDepth;
|
||||
ActiveDepth = depth;
|
||||
|
||||
//if (color != null && depth != null) depth.InheritDepthStencil(color);
|
||||
var gd = GD;
|
||||
gd.SetRenderTarget(color); //can be null
|
||||
if (clear)
|
||||
{
|
||||
StencilValue = 1;
|
||||
|
||||
gd.Clear(clearColor);// FSO.Common.Rendering.Framework.GameScreen.ClearColor);
|
||||
if (depth != null)
|
||||
{
|
||||
gd.SetRenderTarget(depth);
|
||||
gd.Clear(Color.White);
|
||||
}
|
||||
}
|
||||
if (FSOEnvironment.UseMRT)
|
||||
{
|
||||
if (depth != null) gd.SetRenderTargets(color, depth);
|
||||
}
|
||||
}
|
||||
|
||||
public static RenderTarget2D GetBackbuffer()
|
||||
{
|
||||
return Backbuffer;
|
||||
}
|
||||
|
||||
public delegate void RenderPPXProcedureDelegate(bool depthPass);
|
||||
public static void RenderPPXDepth(Effect effect, bool forceDepth,
|
||||
RenderPPXProcedureDelegate proc)
|
||||
{
|
||||
var color = ActiveColor;
|
||||
var depth = ActiveDepth;
|
||||
var gd = GD;
|
||||
if (FSOEnvironment.SoftwareDepth && depth != null)
|
||||
{
|
||||
var oldDS = gd.DepthStencilState;
|
||||
//completely special case.
|
||||
gd.SetRenderTarget(color);
|
||||
gd.DepthStencilState = new DepthStencilState
|
||||
{
|
||||
StencilEnable = true,
|
||||
StencilFunction = CompareFunction.Always,
|
||||
StencilFail = StencilOperation.Keep,
|
||||
StencilPass = StencilOperation.Replace,
|
||||
CounterClockwiseStencilPass = StencilOperation.Replace,
|
||||
StencilDepthBufferFail = StencilOperation.Keep,
|
||||
DepthBufferEnable = forceDepth, //(ActiveColor == null),
|
||||
DepthBufferWriteEnable = forceDepth, //(ActiveColor == null),
|
||||
ReferenceStencil = StencilValue,
|
||||
TwoSidedStencilMode = true
|
||||
};
|
||||
effect.Parameters["depthMap"].SetValue(depth);
|
||||
effect.Parameters["depthOutMode"].SetValue(false);
|
||||
proc(false);
|
||||
|
||||
//now draw the depth using the depth test information we got previously.
|
||||
|
||||
//unbind depth map since we are writing to it
|
||||
effect.Parameters["depthMap"].SetValue((Texture2D)null);
|
||||
effect.Parameters["depthOutMode"].SetValue(true);
|
||||
gd.SetRenderTarget(depth);
|
||||
gd.DepthStencilState = new DepthStencilState
|
||||
{
|
||||
StencilEnable = true,
|
||||
StencilFunction = CompareFunction.Equal,
|
||||
DepthBufferEnable = forceDepth,
|
||||
DepthBufferWriteEnable = forceDepth,
|
||||
ReferenceStencil = StencilValue,
|
||||
};
|
||||
proc(true);
|
||||
|
||||
gd.DepthStencilState = oldDS;
|
||||
StencilValue++; //can increment up to 254 times. Assume we're not going to be rendering that much between clears.
|
||||
if (StencilValue > 255) StencilValue = 1;
|
||||
gd.SetRenderTarget(color);
|
||||
effect.Parameters["depthOutMode"].SetValue(false);
|
||||
}
|
||||
else if (!FSOEnvironment.UseMRT && depth != null)
|
||||
{
|
||||
//draw color then draw depth
|
||||
gd.SetRenderTarget(color);
|
||||
proc(false);
|
||||
effect.Parameters["depthOutMode"].SetValue(true);
|
||||
gd.SetRenderTarget(depth);
|
||||
proc(true);
|
||||
effect.Parameters["depthOutMode"].SetValue(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
//mrt already bound. draw in both.
|
||||
proc(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static Action<GraphicsDevice, RenderTarget2D> SSAAFunc;
|
||||
public static bool WithOpacity = true;
|
||||
|
||||
public static void DrawBackbuffer(float opacity, float scale)
|
||||
{
|
||||
if (Backbuffer == null) return; //this gfx mode does not use a rendertarget backbuffer
|
||||
if (SSAA > 1)
|
||||
{
|
||||
SSAAFunc(GD, Backbuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!WithOpacity)
|
||||
{
|
||||
SB.Begin(blendState: BlendState.Opaque);
|
||||
opacity = 1;
|
||||
}
|
||||
else
|
||||
SB.Begin(blendState: BlendState.AlphaBlend);
|
||||
SB.Draw(Backbuffer, new Vector2(Backbuffer.Width * (1 - scale) / 2, Backbuffer.Height * (1 - scale) / 2), null, Color.White * opacity, 0f, new Vector2(), scale,
|
||||
SpriteEffects.None, 0);
|
||||
SB.End();
|
||||
}
|
||||
}
|
||||
|
||||
public static Point GetWidthHeight()
|
||||
{
|
||||
return new Point(Backbuffer.Width, Backbuffer.Height);
|
||||
}
|
||||
|
||||
public static RenderTarget2D CreateRenderTarget(GraphicsDevice device, int numberLevels, int multisample, SurfaceFormat surface, int width, int height, DepthFormat dformat)
|
||||
{
|
||||
//apparently in xna4, there is no way to check device format... (it looks for the closest format if desired is not supported) need to look into if this affects anything.
|
||||
|
||||
/*MultiSampleType type = device.PresentationParameters.MultiSampleType;
|
||||
|
||||
// If the card can't use the surface format
|
||||
if (!GraphicsAdapter.DefaultAdapter.CheckDeviceFormat(
|
||||
DeviceType.Hardware,
|
||||
GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Format,
|
||||
TextureUsage.None,
|
||||
QueryUsages.None,
|
||||
ResourceType.RenderTarget,
|
||||
surface))
|
||||
{
|
||||
// Fall back to current display format
|
||||
surface = device.DisplayMode.Format;
|
||||
}
|
||||
// Or it can't accept that surface format
|
||||
// with the current AA settings
|
||||
else if (!GraphicsAdapter.DefaultAdapter.CheckDeviceMultiSampleType(
|
||||
DeviceType.Hardware, surface,
|
||||
device.PresentationParameters.IsFullScreen, type))
|
||||
{
|
||||
// Fall back to no antialiasing
|
||||
type = MultiSampleType.None;
|
||||
}*/
|
||||
|
||||
/*int width, height;
|
||||
|
||||
// See if we can use our buffer size as our texture
|
||||
CheckTextureSize(device.PresentationParameters.BackBufferWidth,
|
||||
device.PresentationParameters.BackBufferHeight,
|
||||
out width, out height);*/
|
||||
|
||||
// Create our render target
|
||||
return new RenderTarget2D(device,
|
||||
width, height, (numberLevels>1), surface,
|
||||
DepthFormat.Depth24Stencil8, multisample, RenderTargetUsage.PreserveContents);
|
||||
}
|
||||
}
|
||||
}
|
14
server/tso.common/Utils/PathCaseTools.cs
Executable file
14
server/tso.common/Utils/PathCaseTools.cs
Executable file
|
@ -0,0 +1,14 @@
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
public static class PathCaseTools
|
||||
{
|
||||
public static string Insensitive(string file)
|
||||
{
|
||||
var dir = Directory.GetFiles(Path.GetDirectoryName(file));
|
||||
return dir.FirstOrDefault(x => x.ToLowerInvariant().Replace('\\', '/') == file.ToLowerInvariant().Replace('\\', '/'));
|
||||
}
|
||||
}
|
||||
}
|
35
server/tso.common/Utils/Promise.cs
Executable file
35
server/tso.common/Utils/Promise.cs
Executable file
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
public class Promise <T>
|
||||
{
|
||||
private Func<object, T> Getter;
|
||||
private T Value;
|
||||
private bool HasRun = false;
|
||||
|
||||
|
||||
public Promise(Func<object, T> getter)
|
||||
{
|
||||
this.Getter = getter;
|
||||
}
|
||||
|
||||
public void SetValue(T value)
|
||||
{
|
||||
this.HasRun = true;
|
||||
this.Value = value;
|
||||
}
|
||||
|
||||
|
||||
public T Get()
|
||||
{
|
||||
if (HasRun == false)
|
||||
{
|
||||
Value = Getter(null);
|
||||
HasRun = true;
|
||||
}
|
||||
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
}
|
58
server/tso.common/Utils/StateMachine.cs
Executable file
58
server/tso.common/Utils/StateMachine.cs
Executable file
|
@ -0,0 +1,58 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
public class StateMachine <STATES> where STATES : IConvertible
|
||||
{
|
||||
public STATES CurrentState { get; internal set; }
|
||||
private Dictionary<STATES, List<STATES>> LegalMoves;
|
||||
|
||||
public event Callback<STATES, STATES> OnTransition;
|
||||
|
||||
public StateMachine(STATES startState)
|
||||
{
|
||||
this.CurrentState = startState;
|
||||
}
|
||||
|
||||
|
||||
public bool TransitionTo(STATES state)
|
||||
{
|
||||
|
||||
lock (CurrentState)
|
||||
{
|
||||
if (CurrentState.Equals(state))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/*if (!LegalMoves.ContainsKey(CurrentState))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!LegalMoves[CurrentState].Contains(state))
|
||||
{
|
||||
return false;
|
||||
}*/
|
||||
|
||||
var previousState = CurrentState;
|
||||
this.CurrentState = state;
|
||||
if (OnTransition != null)
|
||||
{
|
||||
OnTransition(previousState, CurrentState);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/*public StateMachine<STATES> AllowTransition(STATES from, STATES to)
|
||||
{
|
||||
if (!LegalMoves.ContainsKey(from))
|
||||
{
|
||||
LegalMoves.Add(from, new List<STATES>());
|
||||
}
|
||||
LegalMoves[from].Add(to);
|
||||
return this;
|
||||
}*/
|
||||
}
|
||||
}
|
27
server/tso.common/Utils/TSOTime.cs
Executable file
27
server/tso.common/Utils/TSOTime.cs
Executable file
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
public class TSOTime
|
||||
{
|
||||
public static Tuple<int,int,int> FromUTC(DateTime time)
|
||||
{
|
||||
//var count = time.Minute * 60 * 1000 + time.Second * 1000 + time.Millisecond;
|
||||
//count *= 8;
|
||||
//count %= 1000 * 60 * 24;
|
||||
|
||||
//var hour = count / (1000 * 60);
|
||||
//var min = (count / 1000) % 60;
|
||||
//var sec = ((count * 60) / 1000) % 60;
|
||||
|
||||
var hour = time.Hour;
|
||||
var min = time.Minute;
|
||||
var sec = time.Second;
|
||||
var ms = time.Millisecond;
|
||||
|
||||
var cycle = (hour % 2 == 1) ? 3600 : 0;
|
||||
cycle += min * 60 + sec;
|
||||
return new Tuple<int, int, int>(cycle / 300, (cycle % 300) / 5, (cycle % 5)*12 + ((ms * 12) / 1000));
|
||||
}
|
||||
}
|
||||
}
|
582
server/tso.common/Utils/TextureGenerator.cs
Executable file
582
server/tso.common/Utils/TextureGenerator.cs
Executable file
|
@ -0,0 +1,582 @@
|
|||
using System;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
public class TextureGenerator //a fun class for cpu generating textures
|
||||
{
|
||||
private static Texture2D PxWhite;
|
||||
private static Texture2D DefaultAdvLight;
|
||||
private static Texture2D PieButtonImg;
|
||||
|
||||
private static Texture2D InteractionInactive;
|
||||
private static Texture2D InteractionActive;
|
||||
private static Texture2D CatalogInactive;
|
||||
private static Texture2D CatalogActive;
|
||||
private static Texture2D CatalogDisabled;
|
||||
private static Texture2D PieBG;
|
||||
private static Texture2D[] WallZBuffer;
|
||||
private static Texture2D[] AirTiles;
|
||||
private static Texture2D MotiveArrow; //actually a diamond, clip to get required direction
|
||||
private static Texture2D TerrainNoise;
|
||||
private static Texture2D UniformNoise;
|
||||
|
||||
public static Texture2D GetPxWhite(GraphicsDevice gd)
|
||||
{
|
||||
if (PxWhite == null) PxWhite = TextureUtils.TextureFromColor(gd, Color.White);
|
||||
return PxWhite;
|
||||
}
|
||||
|
||||
public static Texture2D GetDefaultAdv(GraphicsDevice gd)
|
||||
{
|
||||
if (DefaultAdvLight == null) DefaultAdvLight = TextureUtils.TextureFromColor(gd, new Color(128, 0, 0, 255)); //outdoors color max
|
||||
return DefaultAdvLight;
|
||||
}
|
||||
|
||||
public static Texture2D GetPieButtonImg(GraphicsDevice gd)
|
||||
{
|
||||
if (PieButtonImg == null) PieButtonImg = GenerateRoundedRectangle(gd, new Color(0, 40, 140), 27, 27, 6);
|
||||
return PieButtonImg;
|
||||
}
|
||||
|
||||
public static Texture2D GetInteractionActive(GraphicsDevice gd)
|
||||
{
|
||||
if (InteractionActive == null) InteractionActive = GenerateObjectIconBorder(gd, new Color(255, 255, 0), new Color(56, 88, 120));
|
||||
return InteractionActive;
|
||||
}
|
||||
|
||||
public static Texture2D GetInteractionInactive(GraphicsDevice gd)
|
||||
{
|
||||
if (InteractionInactive == null) InteractionInactive = GenerateObjectIconBorder(gd, new Color(128, 128, 128), new Color(56, 88, 120));
|
||||
return InteractionInactive;
|
||||
}
|
||||
|
||||
public static Texture2D GetCatalogInactive(GraphicsDevice gd)
|
||||
{
|
||||
if (CatalogInactive == null) CatalogInactive = GenerateCatalogIconBorder(gd, new Color(140, 170, 206), new Color(56, 88, 120));
|
||||
return CatalogInactive;
|
||||
}
|
||||
|
||||
public static Texture2D GetCatalogDisabled(GraphicsDevice gd)
|
||||
{
|
||||
if (CatalogDisabled == null) CatalogDisabled = GenerateCatalogIconBorder(gd, new Color(255, 0, 0), new Color(56, 88, 120));
|
||||
return CatalogDisabled;
|
||||
}
|
||||
|
||||
public static Texture2D GetCatalogActive(GraphicsDevice gd)
|
||||
{
|
||||
if (CatalogActive == null) CatalogActive = GenerateCatalogIconBorder(gd, new Color(140, 170, 206), new Color(189, 215, 247));
|
||||
return CatalogActive;
|
||||
}
|
||||
|
||||
public static Texture2D GetPieBG(GraphicsDevice gd)
|
||||
{
|
||||
if (PieBG == null)
|
||||
{
|
||||
PieBG = new Texture2D(gd, 200, 200);
|
||||
Color[] data = new Color[200 * 200];
|
||||
int offset = 0;
|
||||
for (int y = 0; y < 200; y++)
|
||||
{
|
||||
for (int x = 0; x < 200; x++)
|
||||
{
|
||||
data[offset++] = new Color(0, 0, 0, (float)Math.Min(1, 2 - Math.Sqrt(Math.Pow(y - 100, 2) + Math.Pow(x - 100, 2)) / 50) * 0.5f);
|
||||
}
|
||||
}
|
||||
PieBG.SetData<Color>(data);
|
||||
}
|
||||
|
||||
return PieBG;
|
||||
}
|
||||
|
||||
public static Texture2D GetMotiveArrow(GraphicsDevice gd)
|
||||
{
|
||||
if (MotiveArrow == null)
|
||||
{
|
||||
MotiveArrow = new Texture2D(gd, 5, 5);
|
||||
Color[] data = new Color[5 * 5];
|
||||
var size = new Vector2(5, 5);
|
||||
|
||||
FillRect(data, size, new Rectangle(2, 0, 1, 1), Color.White);
|
||||
FillRect(data, size, new Rectangle(1, 1, 3, 1), Color.White);
|
||||
FillRect(data, size, new Rectangle(0, 2, 5, 1), Color.White);
|
||||
FillRect(data, size, new Rectangle(1, 3, 3, 1), Color.White);
|
||||
FillRect(data, size, new Rectangle(2, 4, 1, 1), Color.White);
|
||||
|
||||
MotiveArrow.SetData<Color>(data);
|
||||
}
|
||||
return MotiveArrow;
|
||||
}
|
||||
|
||||
public static Texture2D GetTerrainNoise(GraphicsDevice gd)
|
||||
{
|
||||
if (TerrainNoise == null)
|
||||
{
|
||||
TerrainNoise = new Texture2D(gd, 512, 512, true, SurfaceFormat.Color);
|
||||
Color[] data = new Color[512 * 512];
|
||||
|
||||
var rd = new Random();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
//distribution is an average of two noise functions.
|
||||
data[i].R = (byte)((rd.Next(255) + rd.Next(255)) / 2);
|
||||
data[i].G = (byte)((rd.Next(255) + rd.Next(255)) / 2);
|
||||
data[i].B = (byte)((rd.Next(255) + rd.Next(255)) / 2);
|
||||
data[i].A = (byte)((rd.Next(255) + rd.Next(255)) / 2);
|
||||
}
|
||||
TextureUtils.UploadWithMips(TerrainNoise, gd, data);
|
||||
}
|
||||
return TerrainNoise;
|
||||
}
|
||||
|
||||
public static Texture2D GetUniformNoise(GraphicsDevice gd)
|
||||
{
|
||||
if (UniformNoise == null)
|
||||
{
|
||||
UniformNoise = new Texture2D(gd, 512, 512, true, SurfaceFormat.Color);
|
||||
Color[] data = new Color[512 * 512];
|
||||
|
||||
var rd = new Random();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
//distribution is an average of two noise functions.
|
||||
data[i].R = (byte)(rd.Next(255));
|
||||
data[i].G = (byte)(rd.Next(255));
|
||||
data[i].B = (byte)(rd.Next(255));
|
||||
data[i].A = (byte)(rd.Next(255));
|
||||
}
|
||||
TextureUtils.UploadWithMips(UniformNoise, gd, data);
|
||||
}
|
||||
return UniformNoise;
|
||||
}
|
||||
|
||||
public static float FLAT_Z_INC = 1.525f;
|
||||
public static float[][] WallZBufferConfig = new float[][] {
|
||||
// format: width, height, startIntensity, Xdiff, Ydiff
|
||||
|
||||
new float[] {64, 271, 74, 1, 0.5f}, //near top left
|
||||
new float[] {64, 271, 135, -1, 0.5f}, //near top right
|
||||
new float[] {128, 240, 89.5f, 0, 0.5f}, //near horiz diag
|
||||
new float[] {16, 232, 45, 0, 0.5f}, //near vert diag
|
||||
|
||||
new float[] {32, 135, 74, 2, 1f}, //med top left
|
||||
new float[] {32, 135, 135, -2, 1f}, //med top right
|
||||
new float[] {64, 120, 89.5f, 0, 1f}, //med horiz diag
|
||||
new float[] {8, 116, 45, 0, 1f}, //med vert diag
|
||||
|
||||
new float[] {16, 67, 74, 4, 2f}, //far top left
|
||||
new float[] {16, 67, 135, -4, 2f}, //far top right
|
||||
new float[] {32, 60, 89.5f, 0, 2f}, //far horiz diag
|
||||
new float[] {4, 58, 45, 0, 2f}, //far vert diag
|
||||
|
||||
//12
|
||||
new float[] {128, 64, 255, 0, -FLAT_Z_INC}, //near floor
|
||||
new float[] {64, 32, 255, 0, -FLAT_Z_INC*2}, //med floor
|
||||
new float[] {32, 16, 255, 0, -FLAT_Z_INC*4}, //far floor
|
||||
|
||||
//vert flips of the above
|
||||
//15
|
||||
new float[] {128, 64, 153, 0, FLAT_Z_INC},
|
||||
new float[] {64, 32, 153, 0, FLAT_Z_INC*2},
|
||||
new float[] {32, 16, 153, 0, FLAT_Z_INC*4},
|
||||
|
||||
//18
|
||||
new float[] {128, 64, 263, 0, -FLAT_Z_INC}, //near junction walls up
|
||||
new float[] {64, 32, 263, 0, -FLAT_Z_INC*2}, //med junction walls up
|
||||
new float[] {32, 16, 263, 0, -FLAT_Z_INC*4}, //far junction walls up
|
||||
|
||||
|
||||
//versions for corners (man this is getting complicated)
|
||||
//21
|
||||
//top corner
|
||||
new float[] {43, 22, 254, 0, -FLAT_Z_INC}, //near
|
||||
new float[] {21, 12, 254, 0, -FLAT_Z_INC*2}, //med
|
||||
new float[] {13, 7, 254, 0, -FLAT_Z_INC*4}, //far
|
||||
|
||||
//24
|
||||
//side corner
|
||||
new float[] {35, 21, 254 - (FLAT_Z_INC* 22), 0, -FLAT_Z_INC}, //near
|
||||
new float[] {16, 13, 254 - (FLAT_Z_INC * 22), 0, -FLAT_Z_INC*2}, //med
|
||||
new float[] {11, 8, 254 - (FLAT_Z_INC * 22), 0, -FLAT_Z_INC*4}, //far
|
||||
|
||||
//27
|
||||
new float[] {41, 23, 254 - (FLAT_Z_INC * (64 - 23)), 0, -FLAT_Z_INC}, //near
|
||||
new float[] {18, 13, 254 - (FLAT_Z_INC * (64 - 23)), 0, -FLAT_Z_INC*2}, //med
|
||||
new float[] {9, 8, 254 - (FLAT_Z_INC * (64 - 23)), 0, -FLAT_Z_INC*4}, //far
|
||||
|
||||
//30
|
||||
new float[] {1, 1, 49, 0, 0} //balloon
|
||||
};
|
||||
|
||||
public static Texture2D[] GetWallZBuffer(GraphicsDevice gd)
|
||||
{
|
||||
float bias = 0f;
|
||||
if (WallZBuffer == null)
|
||||
{
|
||||
var count = WallZBufferConfig.Length;
|
||||
WallZBuffer = new Texture2D[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var config = WallZBufferConfig[i];
|
||||
int width = (int)config[0];
|
||||
int height = (int)config[1];
|
||||
|
||||
WallZBuffer[i] = new Texture2D(gd, width, height);
|
||||
Color[] data = new Color[width * height];
|
||||
int offset = 0;
|
||||
|
||||
float yInt = config[2];
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
float xInt = yInt;
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
byte zCol = (byte)Math.Round(Math.Min(255, xInt + bias));
|
||||
data[offset++] = new Color(zCol, zCol, zCol, zCol);
|
||||
xInt += config[3];
|
||||
}
|
||||
yInt += config[4];
|
||||
}
|
||||
WallZBuffer[i].SetData<Color>(data);
|
||||
}
|
||||
}
|
||||
|
||||
return WallZBuffer;
|
||||
}
|
||||
|
||||
public static Texture2D[] GetAirTiles(GraphicsDevice gd)
|
||||
{
|
||||
if (AirTiles == null)
|
||||
{
|
||||
AirTiles = new Texture2D[3];
|
||||
AirTiles[0] = GenerateAirTile(gd, 127, 64);
|
||||
AirTiles[1] = GenerateAirTile(gd, 63, 32);
|
||||
AirTiles[2] = GenerateAirTile(gd, 31, 16);
|
||||
|
||||
}
|
||||
return AirTiles;
|
||||
}
|
||||
|
||||
private static Texture2D GenerateAirTile(GraphicsDevice gd, int width, int height)
|
||||
{
|
||||
var tex = new Texture2D(gd, width+1, height);
|
||||
Color[] data = new Color[width * height];
|
||||
|
||||
int center = width/2;
|
||||
int middleOff = 0;
|
||||
for (int i=0; i<height; i++)
|
||||
{
|
||||
int index = i * width + (center - middleOff);
|
||||
for (int j=0; j<((middleOff==0)?1:2); j++)
|
||||
data[index++] = (i+j > height / 2)?Color.Black:Color.White;
|
||||
for (int j = 0; j < (middleOff * 2) - 3; j++)
|
||||
if (i % 2 == 0 && (i + (center - middleOff)+j) % 4 == 0) data[index++] = Color.Black;
|
||||
else index++;
|
||||
if (middleOff != 0)
|
||||
{
|
||||
for (int j = 0; j < 2; j++)
|
||||
data[index++] = (i + (1-j) > height / 2) ? Color.Black : Color.White;
|
||||
}
|
||||
|
||||
middleOff += (i == height/2-1)?1:((i<height/2)?2:-2);
|
||||
}
|
||||
tex.SetData<Color>(FloorCopy(data, width, height));
|
||||
return tex;
|
||||
}
|
||||
|
||||
public static Color[] FloorCopy(Color[] data, int width, int height)
|
||||
{
|
||||
if (width % 2 != 0)
|
||||
{
|
||||
var target = new Color[(width + 1) * height];
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
Array.Copy(data, y * width, target, y * (width + 1), width);
|
||||
}
|
||||
data = target;
|
||||
width += 1;
|
||||
}
|
||||
var ndat = new Color[data.Length];
|
||||
int hw = (width) / 2;
|
||||
int hh = (height) / 2;
|
||||
int idx = 0;
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
var xp = (x + hw) % width;
|
||||
var yp = (y + hh) % height;
|
||||
var rep = data[xp + yp * width];
|
||||
if (rep.A >= 254) ndat[idx] = rep;
|
||||
else ndat[idx] = data[idx];
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
return ndat;
|
||||
}
|
||||
|
||||
public static Texture2D GenerateObjectIconBorder(GraphicsDevice gd, Color highlight, Color bg)
|
||||
{
|
||||
var tex = new Texture2D(gd, 45, 45);
|
||||
Color[] data = new Color[45*45];
|
||||
var size = new Vector2(45, 45);
|
||||
|
||||
//border
|
||||
FillRect(data, size, new Rectangle(3, 0, 39, 2), highlight);
|
||||
FillRect(data, size, new Rectangle(0, 3, 2, 39), highlight);
|
||||
FillRect(data, size, new Rectangle(3, 43, 39, 2), highlight);
|
||||
FillRect(data, size, new Rectangle(43, 3, 2, 39), highlight);
|
||||
//end border
|
||||
|
||||
//bg
|
||||
FillRect(data, size, new Rectangle(2, 2, 41, 41), bg);
|
||||
//end bg
|
||||
|
||||
//top left rounded
|
||||
FillRect(data, size, new Rectangle(2, 1, 2, 2), highlight);
|
||||
FillRect(data, size, new Rectangle(1, 2, 2, 2), highlight);
|
||||
|
||||
//top right rounded
|
||||
FillRect(data, size, new Rectangle(41, 1, 2, 2), highlight);
|
||||
FillRect(data, size, new Rectangle(42, 2, 2, 2), highlight);
|
||||
|
||||
//btm left rounded
|
||||
FillRect(data, size, new Rectangle(1, 41, 2, 2), highlight);
|
||||
FillRect(data, size, new Rectangle(2, 42, 2, 2), highlight);
|
||||
|
||||
//btm right rounded
|
||||
FillRect(data, size, new Rectangle(41, 42, 2, 2), highlight);
|
||||
FillRect(data, size, new Rectangle(42, 41, 2, 2), highlight);
|
||||
|
||||
tex.SetData<Color>(data);
|
||||
return tex;
|
||||
}
|
||||
|
||||
public static Texture2D GenerateCatalogIconBorder(GraphicsDevice gd, Color highlight, Color bg)
|
||||
{
|
||||
var tex = new Texture2D(gd, 41, 41);
|
||||
Color[] data = new Color[41 * 41];
|
||||
var size = new Vector2(41, 41);
|
||||
|
||||
//border
|
||||
FillRect(data, size, new Rectangle(2, 0, 37, 1), highlight);
|
||||
FillRect(data, size, new Rectangle(0, 2, 1, 37), highlight);
|
||||
FillRect(data, size, new Rectangle(2, 40, 37, 1), highlight);
|
||||
FillRect(data, size, new Rectangle(40, 2, 1, 37), highlight);
|
||||
//end border
|
||||
|
||||
//bg
|
||||
FillRect(data, size, new Rectangle(1, 1, 39, 39), bg);
|
||||
//end bg
|
||||
|
||||
//top left rounded
|
||||
FillRect(data, size, new Rectangle(1, 1, 1, 1), highlight);
|
||||
|
||||
//top right rounded
|
||||
FillRect(data, size, new Rectangle(39, 1, 1, 1), highlight);
|
||||
|
||||
//btm left rounded
|
||||
FillRect(data, size, new Rectangle(1, 39, 1, 1), highlight);
|
||||
|
||||
//btm right rounded
|
||||
FillRect(data, size, new Rectangle(39, 39, 1, 1), highlight);
|
||||
|
||||
tex.SetData<Color>(data);
|
||||
return tex;
|
||||
}
|
||||
|
||||
public static Texture2D GenerateRoundedRectangle(GraphicsDevice gd, Color color, int width, int height, int radius)
|
||||
{
|
||||
var tex = new Texture2D(gd, width, height);
|
||||
Color[] data = new Color[width * height];
|
||||
var size = new Vector2(width, height);
|
||||
|
||||
//rect fills
|
||||
FillRect(data, size, new Rectangle(radius, radius, width - radius * 2, height - radius * 2), color);
|
||||
FillRect(data, size, new Rectangle(radius, 0, width - radius * 2, radius), color);
|
||||
FillRect(data, size, new Rectangle(radius, height-radius, width - radius * 2, radius), color);
|
||||
FillRect(data, size, new Rectangle(0, radius, radius, height-radius*2), color);
|
||||
FillRect(data, size, new Rectangle(width - radius, radius, radius, height - radius * 2), color);
|
||||
|
||||
//corners now
|
||||
for (int i = 0; i < radius; i++)
|
||||
{
|
||||
int seg = (int)Math.Round(Math.Sin(Math.Acos((radius-(i+0.5))/radius))*radius);
|
||||
FillRect(data, size, new Rectangle(radius-seg, i, seg, 1), color);
|
||||
FillRect(data, size, new Rectangle(width-radius, i, seg, 1), color);
|
||||
FillRect(data, size, new Rectangle(radius - seg, height - i - 1, seg, 1), color);
|
||||
FillRect(data, size, new Rectangle(width-radius, height - i - 1, seg, 1), color);
|
||||
}
|
||||
|
||||
tex.SetData<Color>(data);
|
||||
return tex;
|
||||
}
|
||||
|
||||
private static void FillRect(Color[] data, Vector2 texSize, Rectangle dest, Color fillColor)
|
||||
{
|
||||
int x;
|
||||
int y=dest.Y;
|
||||
for (int i = 0; i < dest.Height; i++)
|
||||
{
|
||||
x = dest.X;
|
||||
for (int j = 0; j < dest.Width; j++)
|
||||
{
|
||||
data[y * (int)texSize.X + x] = fillColor;
|
||||
x++;
|
||||
}
|
||||
y++;
|
||||
}
|
||||
}
|
||||
|
||||
private static Texture2D Sun;
|
||||
public static Texture2D GetSun(GraphicsDevice gd)
|
||||
{
|
||||
if (Sun == null)
|
||||
{
|
||||
Sun = new Texture2D(gd, 256, 256);
|
||||
Color[] data = new Color[256 * 256];
|
||||
int offset = 0;
|
||||
for (int y = 0; y < 256; y++)
|
||||
{
|
||||
for (int x = 0; x < 256; x++)
|
||||
{
|
||||
var distance = Math.Sqrt((y - 128) * (y - 128) + (x - 128) * (x - 128));
|
||||
var intensity = (1 - (distance - 25) / 103f);
|
||||
if (intensity < 0) data[offset++] = Color.Transparent;
|
||||
else
|
||||
{
|
||||
intensity *= intensity;
|
||||
data[offset++] = new Color(1, 1, 1, (float)intensity);
|
||||
}
|
||||
}
|
||||
}
|
||||
Sun.SetData<Color>(data);
|
||||
}
|
||||
|
||||
return Sun;
|
||||
}
|
||||
|
||||
private static Texture2D Moon;
|
||||
public static Texture2D GetMoon(GraphicsDevice gd)
|
||||
{
|
||||
if (Moon == null)
|
||||
{
|
||||
Moon = new Texture2D(gd, 64, 64);
|
||||
Color[] data = new Color[64 * 64];
|
||||
int offset = 0;
|
||||
for (int y = 0; y < 64; y++)
|
||||
{
|
||||
for (int x = 0; x < 64; x++)
|
||||
{
|
||||
var distance = Math.Sqrt((y - 32) * (y - 32) + (x - 32) * (x - 32));
|
||||
var intensity = Math.Min(1, Math.Max(0, 32 - distance));
|
||||
|
||||
if (intensity > 0)
|
||||
{
|
||||
//calculate crescent
|
||||
if (x < 32) distance = 0;
|
||||
else distance = Math.Sqrt((y - 32) * (y - 32) + (x - 32)*2 * (x - 32)*2);
|
||||
|
||||
intensity *= 0.2f+(1-Math.Min(1, Math.Max(0, 32 - distance)))*0.8f;
|
||||
}
|
||||
|
||||
data[offset++] = new Color(1, 1, 1, (float)intensity);
|
||||
}
|
||||
}
|
||||
Moon.SetData<Color>(data);
|
||||
}
|
||||
|
||||
return Moon;
|
||||
}
|
||||
|
||||
public static Color FromHSV(float h, float s, float v)
|
||||
{
|
||||
var h2 = (int)h / 60;
|
||||
var chroma = s * v; //times value, but it is always one
|
||||
var X = chroma * (1 - Math.Abs(((h / 60f) % 2) - 1));
|
||||
Color result;
|
||||
switch (h2)
|
||||
{
|
||||
case 0:
|
||||
result = new Color(chroma, X, 0); break;
|
||||
case 1:
|
||||
result = new Color(X, chroma, 0); break;
|
||||
case 2:
|
||||
result = new Color(0, chroma, X); break;
|
||||
case 3:
|
||||
result = new Color(0, X, chroma); break;
|
||||
case 4:
|
||||
result = new Color(X, 0, chroma); break;
|
||||
case 5:
|
||||
result = new Color(chroma, 0, X); break;
|
||||
default:
|
||||
result = Color.Black; break; //undefined
|
||||
}
|
||||
var m = v - chroma;
|
||||
var blend = Color.White * m;
|
||||
result.R += blend.R;
|
||||
result.G += blend.G;
|
||||
result.B += blend.B;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Tuple<float, float, float> ToHSV(Color color)
|
||||
{
|
||||
var r = color.R / 255f;
|
||||
var g = color.G / 255f;
|
||||
var b = color.B / 255f;
|
||||
var min = Math.Min(Math.Min(r, g), b);
|
||||
var max = Math.Max(Math.Max(r, g), b);
|
||||
if (min == max) return new Tuple<float, float, float>(0, 0, min);
|
||||
|
||||
var d = (r == min) ? (g - b) : ((b == min) ? r - g : b - r);
|
||||
var h = (r == min) ? 3 : ((b == min) ? 1 : 5);
|
||||
return new Tuple<float, float, float>(
|
||||
60 * (h - d / (max - min)),
|
||||
(max - min) / max,
|
||||
max);
|
||||
}
|
||||
|
||||
private static Texture2D HSMatrix;
|
||||
public static Texture2D GetHSMatrix(GraphicsDevice gd)
|
||||
{
|
||||
if (HSMatrix == null)
|
||||
{
|
||||
HSMatrix = new Texture2D(gd, 360, 256);
|
||||
Color[] data = new Color[360 * 256];
|
||||
int offset = 0;
|
||||
for (int y = 0; y < 256; y++) //y is saturation
|
||||
{
|
||||
for (int x = 0; x < 360; x++) //x is hue
|
||||
{
|
||||
data[offset++] = FromHSV(x, 1 - (y / 256f), 1f);
|
||||
}
|
||||
}
|
||||
HSMatrix.SetData<Color>(data);
|
||||
}
|
||||
|
||||
return HSMatrix;
|
||||
}
|
||||
|
||||
private static Texture2D HSGrad;
|
||||
public static Texture2D GetHSGrad(GraphicsDevice gd)
|
||||
{
|
||||
if (HSGrad == null)
|
||||
{
|
||||
HSGrad = new Texture2D(gd, 1, 256);
|
||||
Color[] data = new Color[1 * 256];
|
||||
int offset = 0;
|
||||
for (int y = 0; y < 256; y++) //y is saturation
|
||||
{
|
||||
var mod = Color.White * (1 - y / 255f);
|
||||
mod.A = 255;
|
||||
data[offset++] = mod;
|
||||
}
|
||||
HSGrad.SetData<Color>(data);
|
||||
}
|
||||
|
||||
return HSGrad;
|
||||
}
|
||||
}
|
||||
}
|
1049
server/tso.common/Utils/TextureUtils.cs
Executable file
1049
server/tso.common/Utils/TextureUtils.cs
Executable file
File diff suppressed because it is too large
Load diff
193
server/tso.common/Utils/TimedReferenceCache.cs
Executable file
193
server/tso.common/Utils/TimedReferenceCache.cs
Executable file
|
@ -0,0 +1,193 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
public static class TimedReferenceController
|
||||
{
|
||||
private static int CurRingNum = 0;
|
||||
private static List<HashSet<object>> ReferenceRing;
|
||||
private static Dictionary<object, int> ObjectToRing;
|
||||
private static int CheckFreq = 0;
|
||||
private static int TicksToNextCheck;
|
||||
private static CacheType Type;
|
||||
private static object InternalLock = new object { };
|
||||
public static CacheType CurrentType { get { return Type; } }
|
||||
|
||||
static TimedReferenceController()
|
||||
{
|
||||
SetMode(CacheType.ACTIVE);
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
lock (InternalLock)
|
||||
{
|
||||
ObjectToRing = new Dictionary<object, int>();
|
||||
foreach (var item in ReferenceRing)
|
||||
{
|
||||
foreach (var obj in item)
|
||||
{
|
||||
(obj as ITimedCachable)?.Rereferenced(false);
|
||||
}
|
||||
item.Clear();
|
||||
}
|
||||
GC.Collect(2);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetMode(CacheType type)
|
||||
{
|
||||
lock (InternalLock) {
|
||||
ObjectToRing = new Dictionary<object, int>();
|
||||
switch (type)
|
||||
{
|
||||
case CacheType.AGGRESSIVE:
|
||||
ReferenceRing = new List<HashSet<object>>();
|
||||
CheckFreq = 1 * 60;
|
||||
for (int i = 0; i < 5; i++) ReferenceRing.Add(new HashSet<object>());
|
||||
break;
|
||||
case CacheType.ACTIVE:
|
||||
ReferenceRing = new List<HashSet<object>>();
|
||||
CheckFreq = 5 * 60;
|
||||
for (int i = 0; i < 3; i++) ReferenceRing.Add(new HashSet<object>());
|
||||
break;
|
||||
case CacheType.PERMANENT:
|
||||
case CacheType.PASSIVE:
|
||||
ReferenceRing = new List<HashSet<object>>();
|
||||
CheckFreq = int.MaxValue;
|
||||
ReferenceRing.Add(new HashSet<object>());
|
||||
break;
|
||||
}
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Tick()
|
||||
{
|
||||
if (Type == CacheType.PERMANENT) return;
|
||||
if (TicksToNextCheck-- <= 0)
|
||||
{
|
||||
lock (InternalLock)
|
||||
{
|
||||
var toDereference = ReferenceRing[CurRingNum];
|
||||
foreach (var obj in toDereference) ObjectToRing.Remove(obj);
|
||||
toDereference.Clear();
|
||||
CurRingNum = (CurRingNum + 1) % ReferenceRing.Count;
|
||||
}
|
||||
TicksToNextCheck = CheckFreq;
|
||||
//GC.Collect();
|
||||
if (CurRingNum == 0) GC.Collect();
|
||||
}
|
||||
}
|
||||
|
||||
public static void KeepAlive(object o, KeepAliveType type)
|
||||
{
|
||||
if (type == KeepAliveType.ACCESS && (o is ITimedCachable)) ((ITimedCachable)o).Rereferenced(true);
|
||||
//should be called whenever the object is referenced
|
||||
lock (InternalLock)
|
||||
{
|
||||
var offset = ReferenceRing.Count - 1;
|
||||
var becomes = (CurRingNum + offset) % ReferenceRing.Count;
|
||||
int oldring;
|
||||
if (ObjectToRing.TryGetValue(o, out oldring))
|
||||
{
|
||||
if (becomes != oldring)
|
||||
{
|
||||
ReferenceRing[oldring].Remove(o);
|
||||
ObjectToRing.Remove(oldring);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ReferenceRing[becomes].Add(o);
|
||||
ObjectToRing.Add(o, becomes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum KeepAliveType
|
||||
{
|
||||
ACCESS,
|
||||
DEREFERENCED
|
||||
}
|
||||
|
||||
public enum CacheType
|
||||
{
|
||||
AGGRESSIVE,
|
||||
ACTIVE,
|
||||
PASSIVE,
|
||||
PERMANENT
|
||||
}
|
||||
|
||||
|
||||
public class TimedReferenceCache<KEY, VALUE>
|
||||
{
|
||||
private ConcurrentDictionary<KEY, WeakReference> Cache = new ConcurrentDictionary<KEY, WeakReference>();
|
||||
private bool PermaMode = false;
|
||||
private List<VALUE> PermaRef = new List<VALUE>();
|
||||
private VALUE GetOrAddInternal(KEY key, Func<KEY, VALUE> valueFactory)
|
||||
{
|
||||
bool didCreate = false;
|
||||
VALUE created = default(VALUE);
|
||||
var value = Cache.GetOrAdd(key, (k) =>
|
||||
{
|
||||
//ConcurrentDictionary does not ensure we don't accidentally create things twice. This lock will help us, but I don't think it ensures a perfect world.
|
||||
lock (this) {
|
||||
WeakReference prev;
|
||||
if (Cache.TryGetValue(key, out prev) && prev.IsAlive) return prev; //already created this value.
|
||||
created = valueFactory(k);
|
||||
didCreate = true;
|
||||
return new WeakReference(created, true);
|
||||
}
|
||||
});
|
||||
|
||||
var refVal = value.Target;
|
||||
if (didCreate && refVal == (object)created)
|
||||
{
|
||||
//i made this. if we're perma cache ensure a permanent reference.
|
||||
if (TimedReferenceController.CurrentType == CacheType.PERMANENT)
|
||||
{
|
||||
PermaRef.Add(created);
|
||||
PermaMode = true;
|
||||
}
|
||||
return created;
|
||||
}
|
||||
else if (refVal != null || value.IsAlive)
|
||||
return (VALUE)refVal;
|
||||
else
|
||||
{
|
||||
//refrerence died. must recache.
|
||||
WeakReference resultRemove;
|
||||
var removed = Cache.TryRemove(key, out resultRemove);
|
||||
return GetOrAddInternal(key, valueFactory);
|
||||
}
|
||||
}
|
||||
|
||||
public VALUE GetOrAdd(KEY key, Func<KEY, VALUE> valueFactory)
|
||||
{
|
||||
var result = GetOrAddInternal(key, valueFactory);
|
||||
if (result != null && !PermaMode)
|
||||
{
|
||||
TimedReferenceController.KeepAlive(result, KeepAliveType.ACCESS);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool TryRemove(KEY key, out VALUE value)
|
||||
{
|
||||
WeakReference resultRef;
|
||||
if (Cache.TryRemove(key, out resultRef))
|
||||
{
|
||||
value = (VALUE)resultRef.Target;
|
||||
return true;
|
||||
} else
|
||||
{
|
||||
value = default(VALUE);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
53
server/tso.common/Utils/ValuePointer.cs
Executable file
53
server/tso.common/Utils/ValuePointer.cs
Executable file
|
@ -0,0 +1,53 @@
|
|||
using System.Reflection;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Helps UI controls like lists refer to a data service value
|
||||
/// for labels such that when updates come in the labels update
|
||||
/// </summary>
|
||||
public class ValuePointer
|
||||
{
|
||||
private object Item;
|
||||
private PropertyInfo Field;
|
||||
|
||||
public ValuePointer(object item, string field)
|
||||
{
|
||||
this.Item = item;
|
||||
this.Field = item.GetType().GetProperty(field);
|
||||
}
|
||||
|
||||
public object Get()
|
||||
{
|
||||
return Field.GetValue(Item);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var value = Get();
|
||||
if(value != null)
|
||||
{
|
||||
return value.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public static T Get<T>(object value)
|
||||
{
|
||||
if(value == null)
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
|
||||
if(value is ValuePointer)
|
||||
{
|
||||
return (T)((ValuePointer)value).Get();
|
||||
}
|
||||
|
||||
return (T)value;
|
||||
}
|
||||
}
|
||||
}
|
47
server/tso.common/Utils/XMLList.cs
Executable file
47
server/tso.common/Utils/XMLList.cs
Executable file
|
@ -0,0 +1,47 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
|
||||
namespace FSO.Common.Utils
|
||||
{
|
||||
public class XMLList<T> : List<T>, IXMLEntity where T : IXMLEntity
|
||||
{
|
||||
private string NodeName;
|
||||
|
||||
public XMLList(string nodeName)
|
||||
{
|
||||
this.NodeName = nodeName;
|
||||
}
|
||||
|
||||
public XMLList()
|
||||
{
|
||||
this.NodeName = "Unknown";
|
||||
}
|
||||
|
||||
#region IXMLPrinter Members
|
||||
|
||||
public System.Xml.XmlElement Serialize(System.Xml.XmlDocument doc)
|
||||
{
|
||||
var element = doc.CreateElement(NodeName);
|
||||
foreach (var child in this)
|
||||
{
|
||||
element.AppendChild(child.Serialize(doc));
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
public void Parse(System.Xml.XmlElement element)
|
||||
{
|
||||
var type = typeof(T);
|
||||
|
||||
foreach (XmlElement child in element.ChildNodes)
|
||||
{
|
||||
var instance = (T)Activator.CreateInstance(type);
|
||||
instance.Parse(child);
|
||||
this.Add(instance);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue