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:
Tony Bark 2024-05-01 02:55:43 -04:00
parent f12ba1502b
commit 22191ce648
591 changed files with 53264 additions and 3362 deletions

View 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);
}
};
}
}
}

View 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
}
}

View 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));
}
}
}

View 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);
}
}

View 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);
}
}
}

View 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;
}
}

View 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();
}
}

View 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);
}

View 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];
}
}
}

View 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));
}
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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;
}
}
}

View 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);
}
}
}
}
}

View 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();
}
}
}

View 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;
}
}
*/

View file

@ -0,0 +1,7 @@
namespace FSO.Common.Utils
{
public interface ITimedCachable
{
void Rereferenced(bool save);
}
}

View 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;
}
}
}

View 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);
}
}
}
*/

View 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));
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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('\\', '/'));
}
}
}

View 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;
}
}
}

View 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;
}*/
}
}

View 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));
}
}
}

View 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;
}
}
}

File diff suppressed because it is too large Load diff

View 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;
}
}
}
}

View 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;
}
}
}

View 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
}
}