Database Improvements

Brought in all the myriad improvements to the database tools from the Stream Pixels project
Added configurable nested entry grouping
This commit is contained in:
Reinier Vens 2021-04-07 01:27:35 +01:00
commit 968783bf9c
37 changed files with 865 additions and 580 deletions

View file

@ -60,6 +60,7 @@
<Compile Include="Assets\Scripts\ServerShared\Behaviors\HaulingController.cs" />
<Compile Include="Assets\Scripts\ServerShared\NIH\LinqFaster\Reverse.cs" />
<Compile Include="Assets\Scripts\ServerShared\Behaviors\Cooldown.cs" />
<Compile Include="Assets\Scripts\ServerShared\CultCache\RethinkConnection.cs" />
<Compile Include="Assets\Scripts\ServerShared\NIH\LiteNetLib\Utils\NtpPacket.cs" />
<Compile Include="Assets\Scripts\ServerShared\OrbitalEntity.cs" />
<Compile Include="Assets\Scripts\ServerShared\ActionCollection.cs" />
@ -140,7 +141,7 @@
<Compile Include="Assets\Scripts\ServerShared\NIH\LinqFaster\Utils\ComparerMagic.cs" />
<Compile Include="Assets\Scripts\ServerShared\Behaviors\VelocityLimit.cs" />
<Compile Include="Assets\Scripts\ServerShared\Behaviors\Heat.cs" />
<Compile Include="Assets\Scripts\ServerShared\CultCache\Extensions.cs" />
<Compile Include="Assets\Scripts\ServerShared\CultCache\ReflectionExtensions.cs" />
<Compile Include="Assets\Scripts\ServerShared\Behaviors\InstantWeapon.cs" />
<Compile Include="Assets\Scripts\ServerShared\Behaviors\Visibility.cs" />
<Compile Include="Assets\Scripts\ServerShared\NIH\LinqFaster\Repeat.cs" />
@ -195,7 +196,6 @@
<Compile Include="Assets\Scripts\ServerShared\NIH\LinqFaster\Where.cs" />
<Compile Include="Assets\Scripts\ServerShared\NIH\LinqFaster\Min.cs" />
<Compile Include="Assets\Scripts\ServerShared\Behaviors\ParticleEffect.cs" />
<Compile Include="Assets\Scripts\ServerShared\CultCache\MathExtensions.cs" />
<Compile Include="Assets\Scripts\ServerShared\Agents\Tasks\Mining.cs" />
<Compile Include="Assets\Scripts\ServerShared\NIH\MIConvexHull\Vertex2.cs" />
<Compile Include="Assets\Scripts\ServerShared\Sector.cs" />
@ -250,6 +250,9 @@
<Compile Include="Assets\Scripts\ServerShared\NIH\LinqFaster\SequenceEqual.cs" />
<Compile Include="Assets\Scripts\ServerShared\NIH\LinqFaster\Skip.cs" />
<Compile Include="Assets\Scripts\ServerShared\ItemManager.cs" />
<Compile Include="Assets\Scripts\ServerShared\BezierCurve.cs" />
<Compile Include="Assets\Scripts\ServerShared\CultCache\CollectionExtensions.cs" />
<Compile Include="Assets\Scripts\ServerShared\EnumDictionary.cs" />
<None Include="Assets\Scripts\ServerShared\NIH\LinqFaster\readme.txt" />
<None Include="Assets\Scripts\ServerShared\Aetheria.Shared.Unity.asmdef" />
<Reference Include="System.Threading.Tasks.Extensions">
@ -646,6 +649,10 @@
<Project>{4d885cb7-1f9e-abb3-b070-7265b432ecda}</Project>
<Name>UniRx</Name>
</ProjectReference>
<ProjectReference Include="RethinkDB.csproj">
<Project>{f2483ebc-e82f-a58f-b987-c7502f9ab5d9}</Project>
<Name>RethinkDB</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View file

@ -34,10 +34,10 @@ public class AetheriaDatabaseView : DatabaseListView
data => data.HardpointType,
type => Enum.GetName(typeof(HardpointType), type),
(data, type) => data.Hardpoint = type),
// new DatabaseEntryGroup<GearData, Guid>(
// data => data.Manufacturer,
// factionID => cultCache.Get<Faction>(factionID).ShortName,
// (data, faction) => data.Manufacturer = faction),
new DatabaseEntryGroup<GearData, Guid>(
data => data.Manufacturer,
factionID => factionID == Guid.Empty ? "Default" : cultCache.Get<Faction>(factionID).ShortName,
(data, faction) => data.Manufacturer = faction),
new DatabaseEntryGroup<HullData, HullType>(
data => data.HullType,
type => Enum.GetName(typeof(HullType), type),

View file

@ -25,16 +25,19 @@ public class DatabaseInspector : EditorWindow
// Singleton to avoid multiple instances of window.
private static DatabaseInspector _instance;
public static DatabaseInspector Instance => _instance ? _instance : GetWindow<DatabaseInspector>();
private Vector2 _view;
private const int width = 150;
private GUIStyle _warning;
private HashSet<int> _listItemFoldouts = new HashSet<int>();
private Material _schematicMat;
private Dictionary<(Type targetType, HashSet<Type> attributes), (object instance, Type type)> _inspectors =
new Dictionary<(Type targetType, HashSet<Type> attributes), (object instance, Type type)>();
private static bool _listItemStyle;
public static GUIStyle ListItemStyle => (_listItemStyle = !_listItemStyle) ? DatabaseListView.ListStyleEven : DatabaseListView.ListStyleOdd;
private Dictionary<Type, Type> _genericInspectors = new Dictionary<Type, Type>();
public const int Width = 150;
private Vector2 _view;
private GUIStyle _warning;
private List<(Type targetType, HashSet<Type> attributes, Type inspectorType, object instance)> _inspectors =
new List<(Type targetType, HashSet<Type> attributes, Type inspectorType, object instance)>();
private List<(Type targetType, HashSet<Type> attributes, Type inspectorType, Dictionary<Type, object> instances)> _genericInspectors =
new List<(Type targetType, HashSet<Type> attributes, Type inspectorType, Dictionary<Type, object> instances)>();
private HashSet<object> _foldouts = new HashSet<object>();
public Color LabelColor => EditorGUIUtility.isProSkin ? Color.white : Color.black;
@ -57,105 +60,138 @@ public class DatabaseInspector : EditorWindow
{
if (genericArguments[0].ContainsGenericParameters)
{
_genericInspectors.Add(genericArguments[0].GetGenericTypeDefinition(), type);
_genericInspectors.Add((
genericArguments[0].GetGenericTypeDefinition(),
new HashSet<Type>(genericArguments.Skip(1)),
type,
new Dictionary<Type, object>()
));
}
else
{
_inspectors.Add((genericArguments[0], new HashSet<Type>(genericArguments.Skip(1))), (Activator.CreateInstance(type), type));
_inspectors.Add((
genericArguments[0],
new HashSet<Type>(genericArguments.Skip(1)),
type,
Activator.CreateInstance(type)
));
}
}
}
}
public void Inspect(object obj, bool inspectablesOnly = false)
public void Inspect(object obj, bool inspectablesOnly = true)
{
foreach (var field in obj.GetType().GetFields().OrderBy(f=>f.GetCustomAttribute<KeyAttribute>()?.IntKey ?? 0))
{
Inspect(obj, field, inspectablesOnly);
}
// if (obj is EquippableItemData equippableItemData)
// {
// var restricted = false;
// var doubleRestricted = false;
// HullType type = HullType.Ship;
// foreach (var behavior in equippableItemData.Behaviors)
// {
// var restriction = behavior.GetType().GetCustomAttribute<EntityTypeRestrictionAttribute>();
// if (restriction != null)
// {
// if (restricted && restriction.Type != type)
// doubleRestricted = true;
// restricted = true;
// type = restriction.Type;
// }
// }
// if(doubleRestricted)
// GUILayout.Label("ITEM UNEQUIPPABLE: BEHAVIOR HULL TYPE CONFLICT", _warning);
// else if (restricted)
// GUILayout.Label($"Item restricted by behavior to hull type {Enum.GetName(typeof(HullType), type)}", EditorStyles.boldLabel);
// }
}
public void Inspect(object obj, FieldInfo field, bool inspectablesOnly = false)
public void Inspect(object obj, FieldInfo field, bool inspectablesOnly = true)
{
var inspectable = field.GetCustomAttribute<InspectableAttribute>();
if (inspectable == null && inspectablesOnly) return;
var attributes = field.GetCustomAttributes(false);
var type = field.FieldType;
var value = field.GetValue(obj);
field.SetValue(obj, InspectMember(field.Name.SplitCamelCase(), value, obj, type, false, attributes));
}
if (type.IsGenericType)
public object InspectMember(string label, object value, object parent, Type type, bool suppressContainer = false, params object[] attributes)
{
var style = _listItemStyle;
object GetAttribute(Type attributeType)
{
var genericTypeDefinition = type.GetGenericTypeDefinition();
if(_genericInspectors.ContainsKey(genericTypeDefinition))
{
var genericInspectorGenericType = _genericInspectors[genericTypeDefinition];
var genericInspectorType = genericInspectorGenericType.MakeGenericType(type.GenericTypeArguments[0]);
var genericInspector = Activator.CreateInstance(genericInspectorType);
var parameters = new[] {field.Name.SplitCamelCase(), field.GetValue(obj), obj, this};
field.SetValue(obj, genericInspectorType.GetMethod("Inspect").Invoke(genericInspector, parameters));
return;
}
return attributes.FirstOrDefault(attributeType.IsInstanceOfType);
}
var potentialInspectors = _inspectors.Keys
if (value == null && type != typeof(string) && !(type.IsInterface || type.IsAbstract)) value = Activator.CreateInstance(type);
var potentialInspectors = _inspectors
.Where(pi =>
pi.targetType == type &&
pi.attributes.All(attributeType => field.GetCustomAttribute(attributeType) != null));
pi.attributes.All(attributeType => GetAttribute(attributeType) != null));
if (potentialInspectors.Any())
{
var preferredInspector = potentialInspectors.MaxBy(i => i.attributes.Count);
var parameters = new[] {field.Name.SplitCamelCase(), field.GetValue(obj), obj, this}
.Concat(preferredInspector.attributes.Select<Type, object>(attributeType => field.GetCustomAttribute(attributeType)))
var parameters = new[] {label, value, parent, this}
.Concat(preferredInspector.attributes.Select(GetAttribute))
.ToArray();
field.SetValue(obj, _inspectors[preferredInspector].type.GetMethod("Inspect").Invoke(_inspectors[preferredInspector].instance, parameters));
value = preferredInspector.inspectorType.GetMethod("Inspect").Invoke(preferredInspector.instance, parameters);
_listItemStyle = style;
return value;
}
else if (type.IsEnum)
if (type.IsEnum)
{
var isflags = type.GetCustomAttributes<FlagsAttribute>().Any();
var names = Enum.GetNames(field.FieldType);
field.SetValue(obj,
InspectEnum(field.Name.SplitCamelCase(), (int) field.GetValue(obj), isflags ? names.Skip(1).ToArray() : names, isflags));
var names = Enum.GetNames(type);
value = InspectEnum(label, (int) value, isflags ? names.Skip(1).ToArray() : names, isflags);
_listItemStyle = style;
return value;
}
else if (type.GetCustomAttribute<InspectableAttribute>() != null)
if (type.GetCustomAttribute<InspectableAttribute>() != null)
{
Space();
if(!suppressContainer)
GUILayout.BeginVertical(GUI.skin.box);
using (new HorizontalScope())
{
LabelField(field.Name.SplitCamelCase(), EditorStyles.boldLabel);
if (type.IsInterface)
if(!suppressContainer)
{
var subTypes = type.GetAllInterfaceClasses();
if (Foldout(_foldouts.Contains(value), label, true, EditorStyles.boldLabel))
_foldouts.Add(value);
else _foldouts.Remove(value);
}
if (type.IsInterface || type.IsAbstract)
{
var subTypes = type.GetAllChildClasses().Where(t=>!(t.IsInterface || t.IsAbstract)).ToArray();
var selected = value != null ? Array.IndexOf(subTypes, value.GetType()) + 1 : 0;
var newSelection = Popup(selected,
subTypes.Select(t => t.Name.SplitCamelCase()).Prepend("None").ToArray());
if(newSelection != selected)
field.SetValue(obj, value = newSelection==0?null:Activator.CreateInstance(subTypes[newSelection-1]));
} else if(value == null) field.SetValue(obj, value = Activator.CreateInstance(type));
if (newSelection != selected)
value = newSelection == 0 ? null : Activator.CreateInstance(subTypes[newSelection - 1]);
}
else if (value == null) value = Activator.CreateInstance(type);
}
if(value != null) Inspect(field.GetValue(obj), inspectablesOnly);
if (value != null && (_foldouts.Contains(value) || suppressContainer)) Inspect(value);
if(!suppressContainer)
GUILayout.EndVertical();
_listItemStyle = style;
return value;
}
else Debug.Log($"Field \"{field.Name}\" has unknown type {field.FieldType.Name}. No inspector was generated.");
if (type.IsGenericType)
{
var genericTypeDefinition = type.GetGenericTypeDefinition();
var potentialGenericInspectors = _genericInspectors
.Where(pi =>
pi.targetType == genericTypeDefinition &&
pi.attributes.All(attributeType => GetAttribute(attributeType) != null));
if (potentialGenericInspectors.Any())
{
var genericTypeArguments = type.GenericTypeArguments;
var preferredInspector = potentialGenericInspectors.MaxBy(i => i.attributes.Count);
var genericInspectorType = preferredInspector.inspectorType.MakeGenericType(genericTypeArguments);
if (!preferredInspector.instances.ContainsKey(genericInspectorType))
preferredInspector.instances[genericInspectorType] =
Activator.CreateInstance(genericInspectorType);
var parameters = new[] {label, value, parent, this};
value = genericInspectorType.GetMethod("Inspect").Invoke(preferredInspector.instances[genericInspectorType], parameters);
_listItemStyle = style;
return value;
}
}
Debug.Log($"Field \"{label}\" has unknown type {type.Name}. No inspector was generated.");
_listItemStyle = style;
return value;
}
public int InspectEnum(string label, int value, string[] enumOptions, bool flags)
@ -164,13 +200,13 @@ public class DatabaseInspector : EditorWindow
{
Space();
LabelField(label, EditorStyles.boldLabel);
using (var v = new VerticalScope(GUI.skin.box))
using (new VerticalScope(GUI.skin.box))
{
for (var i = 0; i < enumOptions.Length; i++)
{
using (var h = new HorizontalScope())
{
GUILayout.Label(enumOptions[i], GUILayout.Width(width));
GUILayout.Label(enumOptions[i], GUILayout.Width(Width));
var set = Toggle(1 << i == (1 << i & value));
if (set)
value |= 1 << i;
@ -184,7 +220,7 @@ public class DatabaseInspector : EditorWindow
using (var h = new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
GUILayout.Label(label, GUILayout.Width(Width));
return Popup(value, enumOptions);
}
}
@ -212,7 +248,8 @@ public class DatabaseInspector : EditorWindow
// Serialize inspected object so we can check whether it has actually changed
RegisterResolver.Register();
var previousBytes = MessagePackSerializer.Serialize(entry);
var previousBytes = MessagePackSerializer.Serialize<DatabaseEntry>(entry);
var previousHash = previousBytes.GetHashSHA1();
EditorGUI.BeginChangeCheck();
@ -224,7 +261,7 @@ public class DatabaseInspector : EditorWindow
// Debug.Log(entry.ToJson());
if (GUILayout.Button("Delete Entry"))
{
CultCache.Delete(entry);
CultCache.Remove(entry);
entry = null;
return;
}
@ -243,11 +280,12 @@ public class DatabaseInspector : EditorWindow
LabelField(dataEntry.ID.ToString());
}
Inspect(entry, true);
_listItemStyle = false;
Inspect(entry);
if (EditorGUI.EndChangeCheck())
{
if(!previousBytes.ByteEquals(MessagePackSerializer.Serialize(entry)))
if(previousHash != MessagePackSerializer.Serialize(entry).GetHashSHA1())
CultCache.Add(entry);
}

View file

@ -26,8 +26,9 @@ public abstract class DatabaseListView : EditorWindow
protected CultCache cultCache;
private static DatabaseInspector _inspector;
private Type[] _entryTypes;
private bool[] _typeFoldouts;
private Dictionary<string, (bool foldout, (Type entryType, bool foldout)[] types)> _tables;
//private Type[] _entryTypes;
private Type[] _globalTypes;
private string _connectionString;
private Vector2 _view;
private RethinkQueryStatus _queryStatus;
@ -70,6 +71,7 @@ public abstract class DatabaseListView : EditorWindow
public override int HashGroup(object o)
{
return o.GetHashCode();
if (o is K k) return k.GetHashCode();
return 0;
}
@ -94,8 +96,7 @@ public abstract class DatabaseListView : EditorWindow
public Color LabelColor => EditorGUIUtility.isProSkin ? Color.white : Color.black;
private bool _listItemStyle;
public GUIStyle ListItemStyle =>
(_listItemStyle = !_listItemStyle) ? ListStyleEven : ListStyleOdd;
public GUIStyle ListItemStyle => (_listItemStyle = !_listItemStyle) ? ListStyleEven : ListStyleOdd;
void OnEnable()
{
@ -114,19 +115,22 @@ public abstract class DatabaseListView : EditorWindow
cultCache.OnDataUpdateLocal += _ => Repaint();
cultCache.OnDataInsertLocal += _ => Repaint();
cultCache.OnDataDeleteLocal += _ => Repaint();
cultCache.OnDataRemoveLocal += _ => Repaint();
cultCache.OnDataUpdateRemote += _ => EditorDispatcher.Dispatch(Repaint);
cultCache.OnDataInsertRemote += _ => EditorDispatcher.Dispatch(Repaint);
cultCache.OnDataDeleteRemote += _ => EditorDispatcher.Dispatch(Repaint);
cultCache.OnDataRemoveRemote += _ => EditorDispatcher.Dispatch(Repaint);
DatabaseInspector.CultCache = cultCache;
_inspector = DatabaseInspector.Instance;
_inspector.Show();
_entryTypes = typeof(DatabaseEntry).GetAllChildClasses()
.Where(t => t.GetCustomAttribute<InspectableAttribute>() != null).ToArray();
_typeFoldouts = new bool[_entryTypes.Length];
_globalTypes = typeof(DatabaseEntry).GetAllChildClasses()
.Where(t => t.GetCustomAttribute<InspectableAttribute>() != null && t.GetCustomAttribute<GlobalSettingsAttribute>() != null).ToArray();
_tables = typeof(DatabaseEntry).GetAllChildClasses()
.Where(t => t.GetCustomAttribute<InspectableAttribute>() != null && t.GetCustomAttribute<GlobalSettingsAttribute>() == null)
.GroupBy(t=>t.GetCustomAttribute<RethinkTableAttribute>()?.TableName ??
(t.GetCustomAttribute<ExternalEntryAttribute>() != null ? "External" : "Default"))
.ToDictionary(group=>group.Key, group=> (false, group.Select(t=>(t, false)).ToArray()));
if (EditorPrefs.HasKey("RethinkDB.URL"))
_connectionString = EditorPrefs.GetString("RethinkDB.URL");
@ -154,7 +158,7 @@ public abstract class DatabaseListView : EditorWindow
// Indicate that we don't accept drags ourselves
DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
using (var h = new HorizontalScope())
using (new HorizontalScope())
{
_connectionString = TextField(_connectionString);
if (GUILayout.Button("Connect"))
@ -163,13 +167,8 @@ public abstract class DatabaseListView : EditorWindow
JsonKnownTypesSettingsManager.RegisterTypeAssembly<ItemData>();
_queryStatus = RethinkConnection.RethinkConnect(cultCache, _connectionString, DatabaseName);
}
// if (GUILayout.Button("Connect All"))
// {
// EditorPrefs.SetString("RethinkDB.URL", _connectionString);
// _queryStatus = RethinkConnection.RethinkConnect(_databaseCache, _connectionString, true, false);
// }
}
using (var h = new HorizontalScope())
using (new HorizontalScope())
{
//_fileName = TextField(_fileName);
if (GUILayout.Button("Save"))
@ -201,110 +200,132 @@ public abstract class DatabaseListView : EditorWindow
GUI.skin.scrollView,
GUILayout.Width(EditorGUIUtility.currentViewWidth),
GUILayout.ExpandHeight(true));
for (var i = 0; i < _entryTypes.Length; i++)
{
using (var h = new HorizontalScope(ListItemStyle))
{
_typeFoldouts[i] = Foldout(_typeFoldouts[i],
((NameAttribute) _entryTypes[i].GetCustomAttribute(typeof(NameAttribute)))?.Name ?? FormatTypeName(_entryTypes[i].Name),
true);
}
if (_typeFoldouts[i])
foreach (var globalType in _globalTypes)
{
var globalEntry = cultCache.GetGlobal(globalType);
var style = ListItemStyle;
var selected = SelectedItem == globalEntry.ID;
using (new HorizontalScope(selected?SelectedStyle:style))
{
// TODO: NESTED GROUPS!
var entries = cultCache.GetAll(_entryTypes[i])
.OrderBy(entry => entry is INamedEntry namedEntry ? namedEntry.EntryName : entry.ID.ToString());
var grouper = Groupers.FirstOrDefault(g => g.CanGroup(_entryTypes[i]));
if (grouper != null)
using (var h = new HorizontalScope())
{
var groups = entries.GroupBy(o => grouper.SelectGroup(o));
foreach (var group in groups)
if (h.rect.Contains(currentEvent.mousePosition))
{
var groupHash = grouper.HashGroup(group.Key);
var groupName = grouper.GetGroupName(group.Key);
if (currentEventType == EventType.MouseUp)
Select(globalEntry);
}
GUILayout.Label(globalType.Name.SplitCamelCase(), selected ? EditorStyles.whiteLabel : GUI.skin.label);
}
}
}
void DisplayGroup(Type entryType, IEnumerable<DatabaseEntry> entries, IEnumerable<DatabaseEntryGroup> nestedGroups, int indent = 2, Action<DatabaseEntry> activatePreviousGroups = null, string previousGroupNames = "")
{
var grouper = nestedGroups.FirstOrDefault();
if (grouper != null)
{
var groups = entries.GroupBy(o => grouper.SelectGroup(o));
foreach (var group in groups)
{
//var groupHash = grouper.HashGroup(group.Key);
var groupName = grouper.GetGroupName(group.Key);
var groupHash = groupName.GetHashCode();
using (new HorizontalScope(ListItemStyle))
{
GUILayout.Space(indent*10);
if (Foldout(_groupFoldouts.Contains(groupHash), FormatTypeName(groupName), true))
_groupFoldouts.Add(groupHash);
else _groupFoldouts.Remove(groupHash);
}
if (_groupFoldouts.Contains(groupHash))
{
Action<DatabaseEntry> activate = entry =>
{
activatePreviousGroups?.Invoke(entry);
grouper.Activate(entry, group.Key);
};
DisplayGroup(entryType, group, nestedGroups.Skip(1), indent + 1, activate, $"{groupName} {previousGroupNames}");
using (var h = new HorizontalScope(ListItemStyle))
{
GUILayout.Space(10);
if (Foldout(_groupFoldouts.Contains(groupHash), FormatTypeName(groupName), true))
_groupFoldouts.Add(groupHash);
else _groupFoldouts.Remove(groupHash);
}
if (_groupFoldouts.Contains(groupHash))
{
int index = 0;
foreach (var entry in group)
{
index++;
var style = ListItemStyle;
var selected = SelectedItem == entry.ID;
using (new HorizontalScope(selected?SelectedStyle:style))
{
using (var h = new HorizontalScope())
{
if (h.rect.Contains(currentEvent.mousePosition))
{
if (currentEventType == EventType.MouseDrag)
{
DragAndDrop.PrepareStartDrag();
DragAndDrop.SetGenericData("Item", entry.ID);
DragAndDrop.StartDrag("Database Item");
}
else if (currentEventType == EventType.MouseUp)
Select(entry);
}
GUILayout.Space(20);
GUILayout.Label((entry as INamedEntry)?.EntryName ?? index.ToString(), selected ? EditorStyles.whiteLabel : GUI.skin.label);
}
}
}
using (var h = new HorizontalScope(ListItemStyle))
{
if(GUI.Button(h.rect, GUIContent.none, GUIStyle.none))
CreateItem(_entryTypes[i], entry => grouper.Activate(entry, group.Key));
GUILayout.Space(20);
GUILayout.Label($"New {groupName}");
}
if (GUI.Button(h.rect, GUIContent.none, GUIStyle.none))
CreateItem(entryType, activate);
GUILayout.Space((indent+1)*10);
GUILayout.Label($"Create {groupName} {previousGroupNames}");
}
}
}
else
}
else
{
int index = 0;
foreach (var entry in entries)
{
int index = 0;
foreach (var entry in entries)
index++;
var style = ListItemStyle;
var selected = SelectedItem == entry.ID;
using (var h = new HorizontalScope(selected ? SelectedStyle : style))
{
index++;
var style = ListItemStyle;
var selected = SelectedItem == entry.ID;
using (var h = new HorizontalScope(selected?SelectedStyle:style))
if (h.rect.Contains(currentEvent.mousePosition))
{
if (h.rect.Contains(currentEvent.mousePosition))
if (currentEventType == EventType.MouseDrag)
{
if (currentEventType == EventType.MouseDrag)
{
DragAndDrop.PrepareStartDrag();
DragAndDrop.SetGenericData("Item", entry.ID);
DragAndDrop.StartDrag("Database Item");
}
else if (currentEventType == EventType.MouseUp)
Select(entry);
DragAndDrop.PrepareStartDrag();
DragAndDrop.SetGenericData("Item", entry.ID);
DragAndDrop.StartDrag("Database Item");
}
GUILayout.Space(10);
GUILayout.Label((entry as INamedEntry)?.EntryName ?? index.ToString(), selected ? EditorStyles.whiteLabel : GUI.skin.label);
else if (currentEventType == EventType.MouseUp)
Select(entry);
}
GUILayout.Space(indent*10);
GUILayout.Label((entry as INamedEntry)?.EntryName ?? index.ToString(),
selected ? EditorStyles.whiteLabel : GUI.skin.label);
}
}
using (var h = new HorizontalScope(ListItemStyle))
}
}
foreach (var table in _tables.Keys.ToArray())
{
var (tableFoldout, _) = _tables[table];
using (new HorizontalScope(ListItemStyle))
{
_tables[table] = (Foldout(tableFoldout = _tables[table].foldout, table, true), _tables[table].types);
}
if (tableFoldout)
{
for (var i = 0; i < _tables[table].types.Length; i++)
{
if(GUI.Button(h.rect, GUIContent.none, GUIStyle.none))
CreateItem(_entryTypes[i]);
GUILayout.Space(10);
GUILayout.Label($"New {_entryTypes[i].Name}");
var (entryType, typeFoldout) = _tables[table].types[i];
using (new HorizontalScope(ListItemStyle))
{
GUILayout.Space(10);
_tables[table].types[i] = (entryType, typeFoldout = Foldout(typeFoldout,
((NameAttribute) entryType.GetCustomAttribute(typeof(NameAttribute)))?.Name ?? FormatTypeName(entryType.Name),
true));
}
if (typeFoldout)
{
var entries = cultCache.GetAll(entryType)
.OrderBy(entry => entry is INamedEntry namedEntry ? namedEntry.EntryName : entry.ID.ToString());
var groupers = Groupers.Where(g => g.CanGroup(entryType));
DisplayGroup(entryType, entries, groupers);
using (var h = new HorizontalScope(ListItemStyle))
{
if (GUI.Button(h.rect, GUIContent.none, GUIStyle.none))
CreateItem(entryType);
GUILayout.Space(20);
GUILayout.Label($"Create {FormatTypeName(entryType.Name)}");
}
}
}
}
}
@ -312,7 +333,7 @@ public abstract class DatabaseListView : EditorWindow
EndScrollView();
}
private string FormatTypeName(string typeName)
public static string FormatTypeName(string typeName)
{
return (typeName.EndsWith("Data")
? typeName.Substring(0, typeName.Length - 4)

View file

@ -12,7 +12,7 @@ public class AnimationCurveFloat4Inspector : BaseInspector<float4[]>
var val = value != null && value.Length > 0
? value.ToCurve()
: new AnimationCurve();
using (var h = new EditorGUILayout.HorizontalScope())
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
val = CurveField(val, Color.yellow, new Rect(0, 0, 1, 1));
@ -22,6 +22,25 @@ public class AnimationCurveFloat4Inspector : BaseInspector<float4[]>
}
}
public class BezierCurveInspector : BaseInspector<BezierCurve>
{
public override BezierCurve Inspect(string label, BezierCurve value, object parent, DatabaseInspector inspectorWindow)
{
var val = value != null && value.Keys != null && value.Keys.Length > 0
? value.ToCurve()
: new AnimationCurve();
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
val = CurveField(val, Color.yellow, new Rect(0, 0, 1, 1));
}
value.Keys = val.keys.Select(k => float4(k.time, k.value, k.inTangent, k.outTangent)).ToArray();
return value;
}
}
public class AnimationCurveInspect : BaseInspector<AnimationCurve>
{
public override AnimationCurve Inspect(string label, AnimationCurve value, object parent, DatabaseInspector inspectorWindow)

View file

@ -8,32 +8,35 @@ public class DatabaseLinkInspector : BaseInspector<Guid, InspectableDatabaseLink
{
public override Guid Inspect(string label, Guid value, object parent, DatabaseInspector inspectorWindow, InspectableDatabaseLinkAttribute link)
{
LabelField(label, EditorStyles.boldLabel);
var valueEntry = DatabaseInspector.CultCache.Get(value);
using (var h = new HorizontalScope(GUI.skin.box))
using (new HorizontalScope())
{
GUILayout.Label((valueEntry as INamedEntry)?.EntryName ?? valueEntry?.ID.ToString() ?? $"Assign a {link.EntryType.Name} by dragging from the list panel.");
if (h.rect.Contains(Event.current.mousePosition) && (Event.current.type == EventType.DragUpdated || Event.current.type == EventType.DragPerform))
GUILayout.Label(label, GUILayout.Width(width));
var valueEntry = DatabaseInspector.CultCache.Get(value);
using (var h = new HorizontalScope(GUI.skin.box))
{
var guid = (Guid) DragAndDrop.GetGenericData("Item");
var dragEntry = DatabaseInspector.CultCache.Get(guid);
var dragValid = dragEntry != null && link.EntryType.IsInstanceOfType(dragEntry) && dragEntry != valueEntry;
if(Event.current.type == EventType.DragUpdated)
DragAndDrop.visualMode = dragValid ? DragAndDropVisualMode.Copy : DragAndDropVisualMode.Rejected;
else if(Event.current.type == EventType.DragPerform)
GUILayout.Label((valueEntry as INamedEntry)?.EntryName ?? valueEntry?.ID.ToString() ?? $"Drag and drop: {DatabaseListView.FormatTypeName(link.EntryType.Name)}");
if (h.rect.Contains(Event.current.mousePosition) && (Event.current.type == EventType.DragUpdated || Event.current.type == EventType.DragPerform))
{
if (dragValid)
var guid = (Guid) DragAndDrop.GetGenericData("Item");
var dragEntry = DatabaseInspector.CultCache.Get(guid);
var dragValid = dragEntry != null && link.EntryType.IsInstanceOfType(dragEntry) && dragEntry != valueEntry;
if(Event.current.type == EventType.DragUpdated)
DragAndDrop.visualMode = dragValid ? DragAndDropVisualMode.Copy : DragAndDropVisualMode.Rejected;
else if(Event.current.type == EventType.DragPerform)
{
DragAndDrop.AcceptDrag();
GUI.changed = true;
return guid;
if (dragValid)
{
DragAndDrop.AcceptDrag();
GUI.changed = true;
return guid;
}
}
}
}
if (GUILayout.Button("-", GUILayout.Width((EditorGUIUtility.singleLineHeight - 3)*2), GUILayout.Height(EditorGUIUtility.singleLineHeight-3)))
{
return Guid.Empty;
if (GUILayout.Button("-", GUILayout.Width((EditorGUIUtility.singleLineHeight - 3)*2), GUILayout.Height(EditorGUIUtility.singleLineHeight-3)))
{
return Guid.Empty;
}
}
}
@ -46,13 +49,19 @@ public class DatabaseLinkObjectInspector<T> : BaseInspector<DatabaseLink<T>> whe
public override DatabaseLink<T> Inspect(string label, DatabaseLink<T> value, object parent, DatabaseInspector inspectorWindow)
{
if (value == null)
value = new DatabaseLink<T>{Cache = DatabaseInspector.CultCache};
LabelField(label, EditorStyles.boldLabel);
var valueEntry = value.Value;
using (var h = new HorizontalScope(GUI.skin.box))
value = new DatabaseLink<T>();
if(!string.IsNullOrEmpty(label))
{
GUILayout.Label((valueEntry as INamedEntry)?.EntryName ?? valueEntry?.ID.ToString() ?? $"Assign a {typeof(T).Name} by dragging from the list panel.");
GUILayout.BeginHorizontal();
GUILayout.Label(label, GUILayout.Width(width));
}
var valueEntry = value.Value;
using (var h = string.IsNullOrEmpty(label) ? new HorizontalScope() : new HorizontalScope(GUI.skin.box))
{
GUILayout.Label((valueEntry as INamedEntry)?.EntryName ?? valueEntry?.ID.ToString() ?? $"Drag and drop: {DatabaseListView.FormatTypeName(typeof(T).Name)}");
if (h.rect.Contains(Event.current.mousePosition) && (Event.current.type == EventType.DragUpdated || Event.current.type == EventType.DragPerform))
{
var guid = (Guid) DragAndDrop.GetGenericData("Item");
@ -70,11 +79,21 @@ public class DatabaseLinkObjectInspector<T> : BaseInspector<DatabaseLink<T>> whe
}
}
}
if (GUILayout.Button("-", GUILayout.Width((EditorGUIUtility.singleLineHeight - 3)*2), GUILayout.Height(EditorGUIUtility.singleLineHeight-3)))
if(value.LinkID != Guid.Empty)
{
value.LinkID = Guid.Empty;
if (GUILayout.Button("-",
GUILayout.Width((EditorGUIUtility.singleLineHeight - 3) * 2),
GUILayout.Height(EditorGUIUtility.singleLineHeight - 3)))
{
value.LinkID = Guid.Empty;
}
}
}
if(!string.IsNullOrEmpty(label))
{
GUILayout.EndHorizontal();
}
return value;
}
@ -92,11 +111,9 @@ public class DatabaseLinkListInspector : BaseInspector<List<Guid>, InspectableDa
DatabaseInspector inspectorWindow,
InspectableDatabaseLinkAttribute link)
{
Space();
LabelField(label, EditorStyles.boldLabel);
using (var v = new VerticalScope(GUI.skin.box))
{
GUILayout.Label(label, EditorStyles.boldLabel);
if (value.Count == 0)
{
using (new HorizontalScope(ListItemStyle))

View file

@ -7,10 +7,6 @@ using static UnityEditor.EditorGUILayout;
public class FloatDictionaryInspector : BaseInspector<Dictionary<Guid, float>, InspectableDatabaseLinkAttribute>
{
private bool _listItemStyle;
public GUIStyle ListItemStyle =>
(_listItemStyle = !_listItemStyle) ? DatabaseListView.ListStyleEven : DatabaseListView.ListStyleOdd;
public override Dictionary<Guid, float> Inspect(string label,
Dictionary<Guid, float> value,
object parent,
@ -24,14 +20,14 @@ public class FloatDictionaryInspector : BaseInspector<Dictionary<Guid, float>, I
{
if (value.Count == 0)
{
using (new HorizontalScope(ListItemStyle))
using (new HorizontalScope(DatabaseInspector.ListItemStyle))
{
GUILayout.Label("Drag from list to add item");
}
}
foreach (var ingredient in value.ToArray())
{
using (new HorizontalScope(ListItemStyle))
using (new HorizontalScope(DatabaseInspector.ListItemStyle))
{
var entry = DatabaseInspector.CultCache.Get(ingredient.Key);
if (entry == null)
@ -79,10 +75,6 @@ public class FloatDictionaryInspector : BaseInspector<Dictionary<Guid, float>, I
public class RangedFloatDictionaryInspector : BaseInspector<Dictionary<Guid, float>, InspectableDatabaseLinkAttribute, RangedFloatAttribute>
{
private bool _listItemStyle;
public GUIStyle ListItemStyle =>
(_listItemStyle = !_listItemStyle) ? DatabaseListView.ListStyleEven : DatabaseListView.ListStyleOdd;
public override Dictionary<Guid, float> Inspect(string label,
Dictionary<Guid, float> value,
object parent,
@ -97,14 +89,14 @@ public class RangedFloatDictionaryInspector : BaseInspector<Dictionary<Guid, flo
{
if (value.Count == 0)
{
using (new HorizontalScope(ListItemStyle))
using (new HorizontalScope(DatabaseInspector.ListItemStyle))
{
GUILayout.Label("Drag from list to add item");
}
}
foreach (var ingredient in value.ToArray())
{
using (new HorizontalScope(ListItemStyle))
using (new HorizontalScope(DatabaseInspector.ListItemStyle))
{
var entry = DatabaseInspector.CultCache.Get(ingredient.Key);
if (entry == null)
@ -152,10 +144,6 @@ public class RangedFloatDictionaryInspector : BaseInspector<Dictionary<Guid, flo
public class IntDictionaryInspector : BaseInspector<Dictionary<Guid, int>, InspectableDatabaseLinkAttribute>
{
private bool _listItemStyle;
public GUIStyle ListItemStyle =>
(_listItemStyle = !_listItemStyle) ? DatabaseListView.ListStyleEven : DatabaseListView.ListStyleOdd;
public override Dictionary<Guid, int> Inspect(string label,
Dictionary<Guid, int> value,
object parent,
@ -169,14 +157,14 @@ public class IntDictionaryInspector : BaseInspector<Dictionary<Guid, int>, Inspe
{
if (value.Count == 0)
{
using (var h = new HorizontalScope(ListItemStyle))
using (var h = new HorizontalScope(DatabaseInspector.ListItemStyle))
{
GUILayout.Label("Drag from list to add item");
}
}
foreach (var ingredient in value.ToArray())
{
using (var h = new HorizontalScope(ListItemStyle))
using (var h = new HorizontalScope(DatabaseInspector.ListItemStyle))
{
var entry = DatabaseInspector.CultCache.Get(ingredient.Key);
if (entry == null)

View file

@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using static Unity.Mathematics.math;
using static UnityEditor.EditorGUILayout;
public class EnumInspector<T> : BaseInspector<List<T>, InspectableEnumValuesAttribute>
{
public override List<T> Inspect(string label, List<T> list, object parent, DatabaseInspector inspectorWindow, InspectableEnumValuesAttribute attribute)
{
var names = Enum.GetNames(attribute.EnumType);
list ??= new List<T>();
while(list.Count < names.Length) list.Add(Activator.CreateInstance<T>());
while(list.Count > names.Length) list.RemoveAt(list.Count-1);
for (var i = 0; i < names.Length; i++)
inspectorWindow.InspectMember(names[i], list[i], parent, typeof(T));
return list;
}
}
public class EnumDictionaryInspector<E, T> : BaseInspector<EnumDictionary<E, T>> where E : Enum
{
private static HashSet<object> _foldouts = new HashSet<object>();
public override EnumDictionary<E, T> Inspect(string label, EnumDictionary<E, T> dict, object parent, DatabaseInspector inspectorWindow)
{
var names = Enum.GetNames(typeof(E));
if (dict == null || dict.Values.Length != names.Length)
{
var newValue = new T[names.Length];
if(dict != null && dict.Values.Length > 0)
Array.Copy(dict.Values, newValue, min(dict.Values.Length, newValue.Length));
dict.Values = newValue;
}
using (new VerticalScope(GUI.skin.box))
{
if (Foldout(_foldouts.Contains(dict), label, true, EditorStyles.boldLabel))
_foldouts.Add(dict);
else _foldouts.Remove(dict);
if(_foldouts.Contains(dict))
{
for (var i = 0; i < names.Length; i++)
{
using (new VerticalScope(DatabaseInspector.ListItemStyle))
{
dict.Values[i] = (T) inspectorWindow.InspectMember(names[i], dict.Values[i], parent, typeof(T));
}
}
}
}
return dict;
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: da1b15e5564747958639c8f959226c74
timeCreated: 1617335525

View file

@ -14,13 +14,13 @@ public class FloatInspector : BaseInspector<float>
}
}
public class RangedFloatInspector : BaseInspector<float, RangedFloatInspectableAttribute>
public class RangedFloatInspector : BaseInspector<float, InspectableRangedFloatAttribute>
{
public override float Inspect(string label,
float value,
object parent,
DatabaseInspector inspectorWindow,
RangedFloatInspectableAttribute attribute)
InspectableRangedFloatAttribute attribute)
{
using (new HorizontalScope())
{

View file

@ -14,9 +14,9 @@ public class IntInspector : BaseInspector<int>
}
}
public class RangedIntInspector : BaseInspector<int, RangedIntInspectableAttribute>
public class RangedIntInspector : BaseInspector<int, InspectableRangedIntAttribute>
{
public override int Inspect(string label, int value, object parent, DatabaseInspector inspectorWindow, RangedIntInspectableAttribute attribute)
public override int Inspect(string label, int value, object parent, DatabaseInspector inspectorWindow, InspectableRangedIntAttribute attribute)
{
using (new HorizontalScope())
{

View file

@ -9,46 +9,49 @@ using static UnityEditor.EditorGUILayout;
public class ListInspector<T> : BaseInspector<List<T>>
{
private bool _listItemStyle;
private static HashSet<int> _listItemFoldouts = new HashSet<int>();
public GUIStyle ListItemStyle =>
(_listItemStyle = !_listItemStyle) ? DatabaseListView.ListStyleEven : DatabaseListView.ListStyleOdd;
private static HashSet<object> _foldouts = new HashSet<object>();
public override List<T> Inspect(string label, List<T> list, object parent, DatabaseInspector inspectorWindow)
{
var listType = typeof(T);
if (listType.GetCustomAttribute<InspectableAttribute>() != null)
var inspectable = listType.GetCustomAttribute<InspectableAttribute>();
// if (listType.GetCustomAttribute<InspectableAttribute>() != null)
// {
//Space();
using (new VerticalScope(GUI.skin.box))
{
Space();
using (new VerticalScope(GUI.skin.box))
if (list == null)
{
if (list == null)
list = new List<T>();
}
else
{
var sorted = true;
var order = int.MinValue;
foreach(var element in list)
{
list = new List<T>();
if (element == null)
{
list.Remove(element);
return list;
}
var elementType = element.GetType();
var elementOrder = elementType.GetCustomAttribute<OrderAttribute>()?.Order ?? 0;
if (elementOrder < order)
sorted = false;
order = elementOrder;
}
else
if (!sorted)
{
var sorted = true;
var order = int.MinValue;
foreach(var element in list)
{
if (element == null)
{
list.Remove(element);
return list;
}
var elementType = element.GetType();
var elementOrder = elementType.GetCustomAttribute<OrderAttribute>()?.Order ?? 0;
if (elementOrder < order)
sorted = false;
order = elementOrder;
}
if (!sorted)
{
list.Sort((x, y) => (x.GetType().GetCustomAttribute<OrderAttribute>()?.Order ?? 0).CompareTo(y.GetType().GetCustomAttribute<OrderAttribute>()?.Order ?? 0));
}
list.Sort((x, y) => (x.GetType().GetCustomAttribute<OrderAttribute>()?.Order ?? 0).CompareTo(y.GetType().GetCustomAttribute<OrderAttribute>()?.Order ?? 0));
}
}
if (Foldout(_foldouts.Contains(list), label, true, EditorStyles.boldLabel))
_foldouts.Add(list);
else _foldouts.Remove(list);
if (_foldouts.Contains(list))
{
foreach (var o in list)
{
if (o == null)
@ -64,78 +67,65 @@ public class ListInspector<T> : BaseInspector<List<T>>
GUI.backgroundColor = tint.TintColor.ToColor();
}
using (new VerticalScope(ListItemStyle))
using (new VerticalScope(DatabaseInspector.ListItemStyle))
{
using (new HorizontalScope())
{
//if (listType.IsInterface)
if(Foldout(_listItemFoldouts.Contains(o.GetHashCode()), o.ToString(), true))
_listItemFoldouts.Add(o.GetHashCode());
else
_listItemFoldouts.Remove(o.GetHashCode());
if (inspectable != null)
{
if (Foldout(_foldouts.Contains(o), o.ToString(), true, EditorStyles.boldLabel))
_foldouts.Add(o);
else
_foldouts.Remove(o);
}
else inspectorWindow.InspectMember("", o, parent, listType, true);
// var rect = GetControlRect(false,
// GUILayout.Width(EditorGUIUtility.singleLineHeight));
// GUI.DrawTexture(rect, Icons.Instance.minus, ScaleMode.StretchToFill, true, 1,
// LabelColor, 0, 0);
if (GUILayout.Button("-", GUILayout.Width((EditorGUIUtility.singleLineHeight - 3)*2), GUILayout.Height(EditorGUIUtility.singleLineHeight-3)))
{
list.Remove(o);
break;
}
}
if(_listItemFoldouts.Contains(o.GetHashCode()))
{
Space();
inspectorWindow.Inspect(o, true);
}
if(inspectable != null && _foldouts.Contains(o))
inspectorWindow.InspectMember("", o, parent, o.GetType(), true);
}
if(_tinted)
GUI.backgroundColor = originalColor;
}
using (new HorizontalScope(ListItemStyle))
using (new HorizontalScope(DatabaseInspector.ListItemStyle))
{
GUILayout.Label($"Add new {listType.Name}", GUILayout.ExpandWidth(true));
GUILayout.Label($"Add new {listType.GetFullName()}", GUILayout.ExpandWidth(true));
// var rect = GetControlRect(false, GUILayout.Width(EditorGUIUtility.singleLineHeight));
// GUI.DrawTexture(rect, Icons.Instance.plus, ScaleMode.StretchToFill, true, 1, LabelColor, 0, 0);
if (GUILayout.Button("+", GUILayout.Width((EditorGUIUtility.singleLineHeight - 3)*2), GUILayout.Height(EditorGUIUtility.singleLineHeight-3)))
{
if (listType.IsInterface)
if (listType.IsAbstract || listType.IsInterface)
{
var menu = new GenericMenu();
foreach (var childType in listType.GetAllInterfaceClasses())
foreach (var childType in listType.GetAllChildClasses().Where(t=>!t.IsAbstract && !t.IsInterface))
{
menu.AddItem(new GUIContent(childType.Name), false, () =>
{
list.Add((T)Activator.CreateInstance(childType));
});
}
menu.ShowAsContext();
}
else if (listType.IsAbstract)
{
var menu = new GenericMenu();
foreach (var childType in listType.GetAllChildClasses().Where(t=>!t.IsAbstract))
{
menu.AddItem(new GUIContent(childType.Name), false, () =>
{
list.Add((T)Activator.CreateInstance(childType));
var o = (T) Activator.CreateInstance(childType);
_foldouts.Add(o);
list.Add(o);
});
}
menu.ShowAsContext();
}
else
{
list.Add(Activator.CreateInstance<T>());
var o = Activator.CreateInstance<T>();
_foldouts.Add(o);
list.Add(o);
}
}
}
}
}
else Debug.Log($"Field \"{label}\" is a list of non-Inspectable type {listType.Name}. No inspector was generated.");
// }
// else Debug.Log($"Field \"{label}\" is a list of non-Inspectable type {listType.Name}. No inspector was generated.");
return list;
}

View file

@ -3,12 +3,11 @@ using UnityEditor;
using UnityEngine;
using static UnityEditor.EditorGUILayout;
public class Float2Inspector : BaseInspector<float2>
{
public override float2 Inspect(string label, float2 value, object parent, DatabaseInspector inspectorWindow)
{
using (new EditorGUILayout.HorizontalScope())
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
GUILayout.Label("X", GUILayout.Width(labelWidth));
@ -24,7 +23,7 @@ public class Float3Inspector : BaseInspector<float3>
{
public override float3 Inspect(string label, float3 value, object parent, DatabaseInspector inspectorWindow)
{
using (new EditorGUILayout.HorizontalScope())
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
GUILayout.Label("X", GUILayout.Width(labelWidth));
@ -38,11 +37,23 @@ public class Float3Inspector : BaseInspector<float3>
}
}
public class Float3ColorInspector : BaseInspector<float3, InspectableColorAttribute>
{
public override float3 Inspect(string label, float3 value, object parent, DatabaseInspector inspectorWindow, InspectableColorAttribute attribute)
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
return ColorField(value.ToColor()).ToFloat3();
}
}
}
public class Float4Inspector : BaseInspector<float4>
{
public override float4 Inspect(string label, float4 value, object parent, DatabaseInspector inspectorWindow)
{
using (new EditorGUILayout.HorizontalScope())
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
GUILayout.Label("X", GUILayout.Width(labelWidth));
@ -58,11 +69,23 @@ public class Float4Inspector : BaseInspector<float4>
}
}
public class Float4ColorInspector : BaseInspector<float4, InspectableColorAttribute>
{
public override float4 Inspect(string label, float4 value, object parent, DatabaseInspector inspectorWindow, InspectableColorAttribute attribute)
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
return ColorField(value.ToColor()).ToFloat4();
}
}
}
public class Int2Inspector : BaseInspector<int2>
{
public override int2 Inspect(string label, int2 value, object parent, DatabaseInspector inspectorWindow)
{
using (new EditorGUILayout.HorizontalScope())
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
GUILayout.Label("X", GUILayout.Width(labelWidth));
@ -86,7 +109,7 @@ public class Int3Inspector : BaseInspector<int3>
{
public override int3 Inspect(string label, int3 value, object parent, DatabaseInspector inspectorWindow)
{
using (new EditorGUILayout.HorizontalScope())
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
GUILayout.Label("X", GUILayout.Width(labelWidth));
@ -104,7 +127,7 @@ public class Int4Inspector : BaseInspector<int4>
{
public override int4 Inspect(string label, int4 value, object parent, DatabaseInspector inspectorWindow)
{
using (new EditorGUILayout.HorizontalScope())
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
GUILayout.Label("X", GUILayout.Width(labelWidth));

View file

@ -39,7 +39,15 @@ public static class UnityExtensions
{
return new AnimationCurve(keys.Select(v => new Keyframe(v.x, v.y, v.z, v.w)).ToArray());
}
public static AnimationCurve ToCurve(this BezierCurve curve)
{
return new AnimationCurve(curve.Keys.Select(v => new Keyframe(v.x, v.y, v.z, v.w)).ToArray());
}
public static Color ToColor(this float3 v) => new Color(v.x,v.y,v.z);
public static float3 ToFloat3(this Color c) => float3(c.r,c.g,c.b);
public static Color ToColor(this float4 v) => new Color(v.x,v.y,v.z, v.w);
public static float4 ToFloat4(this Color c) => float4(c.r, c.g, c.b, c.a);
}

View file

@ -5,7 +5,7 @@
"MessagePack",
"Unity.Mathematics",
"UniRx",
"CultCache"
"RethinkDB"
],
"includePlatforms": [],
"excludePlatforms": [],

View file

@ -4,26 +4,6 @@
using System;
public class NameAttribute : Attribute
{
public string Name;
public NameAttribute(string name)
{
Name = name;
}
}
public class OrderAttribute : Attribute
{
public int Order;
public OrderAttribute(int order)
{
Order = order;
}
}
[AttributeUsage(AttributeTargets.Class)]
public class EntityTypeRestrictionAttribute : Attribute
{

View file

@ -178,8 +178,8 @@ public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior
public override void Activate()
{
// if(CanFire)
// Trigger();
// base.Activate();
if(CanFire)
Trigger();
base.Activate();
}
}

View file

@ -15,8 +15,8 @@ public class SensorData : BehaviorData
[Inspectable, JsonProperty("sensitivity"), Key(3), RuntimeInspectable]
public PerformanceStat Sensitivity = new PerformanceStat();
[InspectableAnimationCurve, JsonProperty("sensitivityCurve"), Key(4), RuntimeInspectable]
public float4[] SensitivityCurve;
[InspectableAnimationCurve, JsonProperty("sensCurve"), Key(4), RuntimeInspectable]
public BezierCurve SensitivityCurve;
[Inspectable, JsonProperty("pingBoost"), Key(5), RuntimeInspectable]
public PerformanceStat PingBoost;

View file

@ -23,10 +23,10 @@ public abstract class WeaponData : BehaviorData
[Inspectable, JsonProperty("damage"), Key(2), RuntimeInspectable]
public PerformanceStat Damage = new PerformanceStat();
[RangedFloatInspectable(0,1), JsonProperty("penetration"), Key(3), RuntimeInspectable]
[InspectableRangedFloat(0,1), JsonProperty("penetration"), Key(3), RuntimeInspectable]
public PerformanceStat Penetration = new PerformanceStat();
[RangedFloatInspectable(0,1), JsonProperty("damageSpread"), Key(4)]
[InspectableRangedFloat(0,1), JsonProperty("damageSpread"), Key(4)]
public PerformanceStat DamageSpread = new PerformanceStat();
[Inspectable, JsonProperty("minRange"), Key(5), RuntimeInspectable]
@ -35,8 +35,8 @@ public abstract class WeaponData : BehaviorData
[Inspectable, JsonProperty("range"), Key(6), RuntimeInspectable]
public PerformanceStat Range = new PerformanceStat();
[InspectableAnimationCurve, JsonProperty("damageRange"), Key(7), RuntimeInspectable]
public float4[] DamageCurve;
[InspectableAnimationCurve, JsonProperty("damageCurve"), Key(7), RuntimeInspectable]
public BezierCurve DamageCurve;
[InspectablePrefab, JsonProperty("effect"), Key(8)]
public string EffectPrefab;

View file

@ -0,0 +1,123 @@
using System;
using MessagePack;
using Newtonsoft.Json;
using Unity.Mathematics;
using static Unity.Mathematics.math;
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class BezierCurve
{
[JsonProperty("keys"), Key(0)] public float4[] Keys;
[IgnoreMember] private int2 _cachedIndices;
// Integrate area under curve between start and end time
public float IntegrateCurve(float startTime, float endTime, int steps)
{
return Integrate(Evaluate, startTime, endTime, steps);
}
// Integrate function f(x) using the trapezoidal rule between x=x_low..x_high
public static float Integrate(Func<float, float> f, float x_low, float x_high, int N_steps)
{
float h = (x_high - x_low) / N_steps;
float res = (f(x_low) + f(x_high)) / 2;
for (int i = 1; i < N_steps; i++)
{
res += f(x_low + i * h);
}
return h * res;
}
// public static float Screen(float a, float b)
// {
// return 1 - (1 - a)*(1 - b);
// }
public float Evaluate(float time)
{
// Clamp time
time = clamp(time, Keys[0].x, Keys[Keys.Length - 1].x);
FindSurroundingKeyframes(time, Keys);
return HermiteInterpolate(time, Keys[_cachedIndices.x], Keys[_cachedIndices.y]);
}
void FindSurroundingKeyframes(float time, float4[] curve)
{
// Check that time is within cached keyframe time
if (time >= curve[_cachedIndices.x].x &&
time <= curve[_cachedIndices.y].x)
{
return;
}
// Fall back to using dichotomic search.
var length = curve.Length;
int half;
int middle;
int first = 0;
while (length > 0)
{
half = length >> 1;
middle = first + half;
if (time < curve[middle].x)
{
length = half;
}
else
{
first = middle + 1;
length = length - half - 1;
}
}
// If not within range, we pick the last element twice.
var indices = int2(first - 1, min(curve.Length - 1, first));
_cachedIndices = indices;
}
float HermiteInterpolate(float time, in float4 leftKeyframe, in float4 rightKeyframe)
{
// Handle stepped curve.
if (isinf(leftKeyframe.w) || isinf(rightKeyframe.z))
{
return leftKeyframe.y;
}
float dx = rightKeyframe.x - leftKeyframe.x;
float m0;
float m1;
float t;
if (dx != 0.0f)
{
t = (time - leftKeyframe.x) / dx;
m0 = leftKeyframe.w * dx;
m1 = rightKeyframe.z * dx;
}
else
{
t = 0.0f;
m0 = 0;
m1 = 0;
}
return HermiteInterpolate(t, leftKeyframe.y, m0, m1, rightKeyframe.y);
}
static float HermiteInterpolate(float t, float p0, float m0, float m1, float p1)
{
// Unrolled the equations to avoid precision issue.
// (2 * t^3 -3 * t^2 +1) * p0 + (t^3 - 2 * t^2 + t) * m0 + (-2 * t^3 + 3 * t^2) * p1 + (t^3 - t^2) * m1
var a = 2.0f * p0 + m0 - 2.0f * p1 + m1;
var b = -3.0f * p0 - 2.0f * m0 + 3.0f * p1 - m1;
var c = m0;
var d = p0;
return t * (t * (a * t + b) + c) + d;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cdb4803cfcd147f40a7dbb0343aebedf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,5 +1,8 @@
using System;
[AttributeUsage(AttributeTargets.Class)]
public class GlobalSettingsAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Class)]
public class ExternalEntryAttribute : Attribute { }
@ -8,15 +11,6 @@ public class InspectableAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Field)]
public abstract class PreferredInspectorAttribute : InspectableAttribute { }
public class InspectableUnityObjectAttribute : PreferredInspectorAttribute
{
public Type ObjectType;
public InspectableUnityObjectAttribute(Type type)
{
ObjectType = type;
}
}
public class InspectableTextAttribute : PreferredInspectorAttribute { }
public class InspectablePrefabAttribute : PreferredInspectorAttribute { }
public class InspectableTextureAttribute : PreferredInspectorAttribute { }
@ -25,6 +19,16 @@ public class InspectableTemperatureAttribute : PreferredInspectorAttribute { }
public class InspectableAnimationCurveAttribute : PreferredInspectorAttribute { }
public class InspectableColorAttribute : PreferredInspectorAttribute { }
public class InspectableEnumValuesAttribute : PreferredInspectorAttribute
{
public Type EnumType;
public InspectableEnumValuesAttribute(Type enumType)
{
EnumType = enumType;
}
}
public class InspectableDatabaseLinkAttribute : PreferredInspectorAttribute
{
public readonly Type EntryType;
@ -35,22 +39,22 @@ public class InspectableDatabaseLinkAttribute : PreferredInspectorAttribute
}
}
public class RangedFloatInspectableAttribute : PreferredInspectorAttribute
public class InspectableRangedFloatAttribute : PreferredInspectorAttribute
{
public readonly float Min, Max;
public RangedFloatInspectableAttribute(float min, float max)
public InspectableRangedFloatAttribute(float min, float max)
{
Min = min;
Max = max;
}
}
public class RangedIntInspectableAttribute : PreferredInspectorAttribute
public class InspectableRangedIntAttribute : PreferredInspectorAttribute
{
public readonly int Min, Max;
public RangedIntInspectableAttribute(int min, int max)
public InspectableRangedIntAttribute(int min, int max)
{
Min = min;
Max = max;
@ -107,4 +111,24 @@ public class RethinkTableAttribute : Attribute
{
TableName = tableName;
}
}
public class NameAttribute : Attribute
{
public string Name;
public NameAttribute(string name)
{
Name = name;
}
}
public class OrderAttribute : Attribute
{
public int Order;
public OrderAttribute(int order)
{
Order = order;
}
}

View file

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
public static class CollectionExtensions
{
// https://stackoverflow.com/a/800469
public static string GetHashSHA1(this byte[] data)
{
using (var sha1 = new System.Security.Cryptography.SHA1CryptoServiceProvider())
{
return string.Concat(sha1.ComputeHash(data).Select(x => x.ToString("X2")));
}
}
// https://stackoverflow.com/a/3188835
public static T MaxBy<T, U>(this IEnumerable<T> items, Func<T, U> selector)
{
if (!items.Any())
{
throw new InvalidOperationException("Empty input sequence");
}
var comparer = Comparer<U>.Default;
T maxItem = items.First();
U maxValue = selector(maxItem);
foreach (T item in items.Skip(1))
{
// Get the value of the item and compare it to the current max.
U value = selector(item);
if (comparer.Compare(value, maxValue) > 0)
{
maxValue = value;
maxItem = item;
}
}
return maxItem;
}
public static T MinBy<T, U>(this IEnumerable<T> items, Func<T, U> selector)
{
if (!items.Any())
{
throw new InvalidOperationException("Empty input sequence");
}
var comparer = Comparer<U>.Default;
T maxItem = items.First();
U maxValue = selector(maxItem);
foreach (T item in items.Skip(1))
{
// Get the value of the item and compare it to the current max.
U value = selector(item);
if (comparer.Compare(value, maxValue) < 0)
{
maxValue = value;
maxItem = item;
}
}
return maxItem;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eed05df00a9a6b84292c81be1662a66e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -14,10 +14,10 @@ public class CultCache
{
public event Action<DatabaseEntry> OnDataInsertLocal;
public event Action<DatabaseEntry> OnDataUpdateLocal;
public event Action<DatabaseEntry> OnDataDeleteLocal;
public event Action<DatabaseEntry> OnDataRemoveLocal;
public event Action<DatabaseEntry> OnDataInsertRemote;
public event Action<DatabaseEntry> OnDataUpdateRemote;
public event Action<DatabaseEntry> OnDataDeleteRemote;
public event Action<DatabaseEntry> OnDataRemoveRemote;
private readonly object addLock = new object();
@ -26,6 +26,7 @@ public class CultCache
private readonly Dictionary<Guid, DatabaseEntry> _entries = new Dictionary<Guid, DatabaseEntry>();
private readonly Dictionary<Type, DatabaseEntry> _globals = new Dictionary<Type, DatabaseEntry>();
private readonly Dictionary<Type, HashSet<DatabaseEntry>> _types = new Dictionary<Type, HashSet<DatabaseEntry>>();
private readonly Dictionary<Type, (DirectoryInfo directory, List<Guid> entries)> _externalTypes = new Dictionary<Type, (DirectoryInfo directory, List<Guid> entries)>();
@ -48,6 +49,7 @@ public class CultCache
public CultCache(string filePath)
{
DatabaseLinkBase.Cache = this;
RegisterResolver.Register();
_filePath = filePath;
var fileInfo = new FileInfo(filePath);
@ -61,6 +63,7 @@ public class CultCache
var linkFields = type.GetFields().Where(f => f.FieldType.IsAssignableToGenericType(typeof(DatabaseLink<>))).ToArray();
if(linkFields.Length > 0)
_linkFields[type] = linkFields;
if (type.GetCustomAttribute<ExternalEntryAttribute>() != null)
{
var typeDirectory = dataDirectory.CreateSubdirectory(type.Name);
@ -79,6 +82,12 @@ public class CultCache
}
}
else _types[type] = new HashSet<DatabaseEntry>();
if (type.GetCustomAttribute<GlobalSettingsAttribute>() != null)
{
_globals[type] = null;
Add(Activator.CreateInstance(type) as DatabaseEntry);
}
}
}
@ -90,19 +99,9 @@ public class CultCache
{
var exists = _entries.ContainsKey(entry.ID);
// If an external entry is added, store it separately
var type = entry.GetType();
if (_linkFields.ContainsKey(type))
{
foreach (var linkField in _linkFields[type])
{
var link = (DatabaseLinkBase) linkField.GetValue(entry);
if (link != null)
{
link.Cache = this;
}
}
}
// If an external entry is added, store it separately
if (_externalTypes.ContainsKey(type))
{
if (_externalEntries.ContainsKey(entry.ID))
@ -119,6 +118,16 @@ public class CultCache
}
else
{
if (_globals.ContainsKey(type))
{
if(_globals[type]!=null)
{
Remove(_globals[type], remote);
exists = true;
}
_globals[type] = entry;
}
_entries[entry.ID] = entry;
_types[type].Add(entry);
foreach (var parentType in type.GetParentTypes())
@ -146,6 +155,9 @@ public class CultCache
}
}
public bool IsExternal(DatabaseEntry entry) => _externalTypes.ContainsKey(entry.GetType());
public bool IsGlobal(DatabaseEntry entry) => _globals.ContainsKey(entry.GetType());
public void AddAll(IEnumerable<DatabaseEntry> entries, bool remote = false)
{
foreach (var entry in entries)
@ -178,6 +190,17 @@ public class CultCache
return Get(guid) as T;
}
public DatabaseEntry GetGlobal(Type type)
{
if (_globals.ContainsKey(type)) return _globals[type];
return null;
}
public T GetGlobal<T>() where T : DatabaseEntry
{
return GetGlobal(typeof(T)) as T;
}
public IEnumerable<DatabaseEntry> GetAll(Type type)
{
if (_externalTypes.ContainsKey(type))
@ -197,14 +220,19 @@ public class CultCache
return !_types.ContainsKey(type) ? Enumerable.Empty<T>() : _types[type].Cast<T>();
}
public void Delete(DatabaseEntry entry, bool remote = false)
public void Remove(DatabaseEntry entry, bool remote = false)
{
_entries.Remove(entry.ID);
foreach (var parentType in entry.GetType().GetParentTypes())
{
if(_types.ContainsKey(parentType))
_types[parentType].Remove(entry);
}
if (remote)
OnDataDeleteRemote?.Invoke(entry);
OnDataRemoveRemote?.Invoke(entry);
else
OnDataDeleteLocal?.Invoke(entry);
OnDataRemoveLocal?.Invoke(entry);
}
public void Load()

View file

@ -55,6 +55,17 @@ public abstract class DatabaseEntry
{
[JsonProperty("id"), Key(0)]
public Guid ID = Guid.NewGuid();
public override int GetHashCode()
{
return ID.GetHashCode();
}
public override bool Equals(object obj)
{
if (obj is DatabaseEntry entry) return entry.ID == ID;
return false;
}
}
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
@ -71,5 +82,10 @@ public class DatabaseLinkBase
public Guid LinkID;
[IgnoreMember]
public CultCache Cache;
public static CultCache Cache;
}
public interface ITintInspector
{
float3 TintColor { get; }
}

View file

@ -1,4 +0,0 @@
public static class MathExtensions
{
}

View file

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 5effe288f2074414930e040d4531709f
timeCreated: 1617057296

View file

@ -2,9 +2,11 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
public static class CultCacheExtensions
public static class ReflectionExtensions
{
private static Dictionary<Type,Type[]> InterfaceClasses = new Dictionary<Type, Type[]>();
public static Type[] GetAllInterfaceClasses(this Type type)
@ -85,9 +87,34 @@ public static class CultCacheExtensions
return IsAssignableToGenericType(baseType, genericType);
}
public static async void WrapAwait(this Task task)
public static string SplitCamelCase( this string str )
{
await task;
return Regex.Replace(
Regex.Replace(
str,
@"(\P{Ll})(\P{Ll}\p{Ll})",
"$1 $2"
),
@"(\p{Ll})(\P{Ll})",
"$1 $2"
);
}
public static string FormatTypeName(this string typeName)
{
return (typeName.EndsWith("Data")
? typeName.Substring(0, typeName.Length - 4)
: typeName).SplitCamelCase();
}
public static string GetFullName(this Type t)
{
if (!t.IsGenericType)
return t.Name.FormatTypeName();
var name = t.Name.FormatTypeName();
return
$"{name.Substring(0, name.LastIndexOf("`"))}{t.GetGenericArguments().Aggregate("<", (aggregate, type) => $"{aggregate}{(aggregate == "<" ? "" : ",")}{GetFullName(type)}")}>";
}
}

View file

@ -12,7 +12,6 @@ using Newtonsoft.Json;
using RethinkDb.Driver;
using RethinkDb.Driver.Ast;
using RethinkDb.Driver.Net;
using UnityEngine;
public static class RethinkConnection
{
@ -47,7 +46,6 @@ public static class RethinkConnection
var connection = R.Connection()
.Hostname(connectionString.Substring(0,connectionStringDomainLength))
.Port(port).Timeout(60).Connect();
Debug.Log("Connected to RethinkDB");
var tables = R.Db(dbName).TableList().RunAtom<string[]>(connection);
@ -61,35 +59,32 @@ public static class RethinkConnection
{
var table = entry.GetType().GetCustomAttribute<RethinkTableAttribute>()?.TableName ?? "Other";
var result = await R
.Db("Aetheria")
.Db(dbName)
.Table(table)
.Get(entry.ID)
.Replace(entry)
.RunAsync(connection);
Debug.Log($"Uploaded entry to RethinkDB: {entry.ID} result: {result}");
};
cache.OnDataInsertLocal += async entry =>
{
var table = entry.GetType().GetCustomAttribute<RethinkTableAttribute>()?.TableName ?? "Other";
var result = await R
.Db("Aetheria")
.Db(dbName)
.Table(table)
.Insert(entry)
.RunAsync(connection);
Debug.Log($"Inserted entry to RethinkDB: {entry.ID} result: {result}");
};
cache.OnDataDeleteLocal += async entry =>
cache.OnDataRemoveLocal += async entry =>
{
var table = entry.GetType().GetCustomAttribute<RethinkTableAttribute>()?.TableName ?? "Other";
var result = await R
.Db("Aetheria")
.Db(dbName)
.Table(table)
.Get(entry.ID)
.Delete()
.RunAsync(connection);
Debug.Log($"Deleted entry from RethinkDB: {entry.ID} result: {result}");
};
}
@ -101,18 +96,17 @@ public static class RethinkConnection
Task.Run(async () =>
{
status.TotalEntries += R
.Db("Aetheria")
.Db(dbName)
.Table(table)
.Count().RunAtom<int>(connection);
var result = await R
.Db("Aetheria")
.Db(dbName)
.Table(table)
.RunCursorAsync<DatabaseEntry>(connection);
while (await result.MoveNextAsync())
{
var entry = result.Current;
//Debug.Log($"Received Items entry from RethinkDB: {entry.GetType()} {(entry as INamedEntry)?.EntryName ?? ""}:{entry.ID}");
cache.Add(entry, true);
status.RetrievedEntries++;
}
@ -122,34 +116,30 @@ public static class RethinkConnection
Task.Run(async () =>
{
var result = await R
.Db("Aetheria")
.Db(dbName)
.Table(table)
.Changes()
.RunChangesAsync<DatabaseEntry>(connection);
while (await result.MoveNextAsync())
{
var change = result.Current;
if(change.OldValue == null)
Debug.Log($"Received change from RethinkDB (Entry Created): {change.NewValue.ID}");
else if(change.NewValue == null)
Debug.Log($"Received change from RethinkDB (Entry Deleted): {change.OldValue.ID}");
else
Debug.Log($"Received change from RethinkDB: {change.NewValue.ID}");
cache.Add(change.NewValue, true);
}
}).WrapAwait();
}
// Observable.Timer(DateTimeOffset.Now, TimeSpan.FromSeconds(60)).Subscribe(_ =>
// {
// Debug.Log(R.Now().Run<DateTime>(connection).ToString() as string);
// });
return status;
}
}
public static class AsyncAwaitExtensions
{
public static async void WrapAwait(this Task task)
{
await task;
}
}
public class RethinkQueryStatus
{
public int RetrievedEntries;

View file

@ -1,7 +1,6 @@
using System.Collections;
using System.Collections.Generic;
using MessagePack;
using MessagePack.ReactivePropertyExtension;
using MessagePack.Resolvers;
public static class RegisterResolver
@ -16,8 +15,7 @@ public static class RegisterResolver
MathResolver.Instance,
TypeResolver.Instance,
NativeGuidResolver.Instance,
StandardResolver.Instance,
ReactivePropertyResolver.Instance
StandardResolver.Instance
);
var options = MessagePackSerializerOptions.Standard.WithResolver(resolver);
MessagePackSerializer.DefaultOptions = options;

View file

@ -0,0 +1,16 @@
using System;
using MessagePack;
using Newtonsoft.Json;
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class EnumDictionary<E, T> where E : Enum
{
[JsonProperty("values"), Key(0)] public T[] Values;
public EnumDictionary()
{
Values = new T[Enum.GetNames(typeof(E)).Length];
}
public T this[E key] => Values[Convert.ToInt32(key)];
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 96c57746cba54d3f9933d93b3242df04
timeCreated: 1617745268

View file

@ -22,15 +22,6 @@ public static class Extensions
{
return baseType.GetInterfaces().Any(interfaceType.Equals);
}
// https://stackoverflow.com/a/800469
public static string GetHashSHA1(this byte[] data)
{
using (var sha1 = new System.Security.Cryptography.SHA1CryptoServiceProvider())
{
return string.Concat(sha1.ComputeHash(data).Select(x => x.ToString("X2")));
}
}
public static void Send<T>(this NetPeer peer, T message, DeliveryMethod method = DeliveryMethod.ReliableOrdered) where T : Message
{
@ -125,26 +116,6 @@ public static class Extensions
throw new ArgumentOutOfRangeException(nameof(rotation), rotation, null);
}
}
public static string SplitCamelCase( this string str )
{
return Regex.Replace(
Regex.Replace(
str,
@"(\P{Ll})(\P{Ll}\p{Ll})",
"$1 $2"
),
@"(\p{Ll})(\P{Ll})",
"$1 $2"
);
}
public static string FormatTypeName(this string typeName)
{
return (typeName.EndsWith("Data")
? typeName.Substring(0, typeName.Length - 4)
: typeName).SplitCamelCase();
}
public static string SignificantDigits(this float d, int digits=10)
{
@ -177,152 +148,11 @@ public static class Extensions
return pair.Equals(new KeyValuePair<T, TU>());
}
private static Dictionary<float4[], int2> _cachedIndices = new Dictionary<float4[], int2>();
public static float Evaluate(this float4[] curve, float time)
{
// Clamp time
time = math.clamp(time, curve[0].x, curve[curve.Length - 1].x);
FindSurroundingKeyframes(time, curve);
return HermiteInterpolate(time, curve[_cachedIndices[curve].x], curve[_cachedIndices[curve].y]);
}
static void FindSurroundingKeyframes(float time, float4[] curve)
{
// Check that time is within cached keyframe time
if (_cachedIndices.ContainsKey(curve) &&
_cachedIndices[curve].x != _cachedIndices[curve].y &&
time >= curve[_cachedIndices[curve].x].x &&
time <= curve[_cachedIndices[curve].y].x)
{
return;
}
// Fall back to using dichotomic search.
var length = curve.Length;
int half;
int middle;
int first = 0;
while (length > 0)
{
half = length >> 1;
middle = first + half;
if (time < curve[middle].x)
{
length = half;
}
else
{
first = middle + 1;
length = length - half - 1;
}
}
// If not within range, we pick the last element twice.
var indices = int2(first - 1, math.min(curve.Length - 1, first));
_cachedIndices[curve] = indices;
}
static float HermiteInterpolate(float time, in float4 leftKeyframe, in float4 rightKeyframe)
{
// Handle stepped curve.
if (math.isinf(leftKeyframe.w) || math.isinf(rightKeyframe.z))
{
return leftKeyframe.y;
}
float dx = rightKeyframe.x - leftKeyframe.x;
float m0;
float m1;
float t;
if (dx != 0.0f)
{
t = (time - leftKeyframe.x) / dx;
m0 = leftKeyframe.w * dx;
m1 = rightKeyframe.z * dx;
}
else
{
t = 0.0f;
m0 = 0;
m1 = 0;
}
return HermiteInterpolate(t, leftKeyframe.y, m0, m1, rightKeyframe.y);
}
static float HermiteInterpolate(float t, float p0, float m0, float m1, float p1)
{
// Unrolled the equations to avoid precision issue.
// (2 * t^3 -3 * t^2 +1) * p0 + (t^3 - 2 * t^2 + t) * m0 + (-2 * t^3 + 3 * t^2) * p1 + (t^3 - t^2) * m1
var a = 2.0f * p0 + m0 - 2.0f * p1 + m1;
var b = -3.0f * p0 - 2.0f * m0 + 3.0f * p1 - m1;
var c = m0;
var d = p0;
return t * (t * (a * t + b) + c) + d;
}
public static float Angle(this float2 from, float2 to)
{
var num = sqrt(lengthsq(from) * lengthsq(to));
return num < 1.00000000362749E-15 ? 0.0f : acos(clamp(dot(from, to) / num, -1f, 1f)) * 57.29578f;
}
// https://stackoverflow.com/a/3188835
public static T MaxBy<T, U>(this IEnumerable<T> items, Func<T, U> selector)
{
if (!items.Any())
{
throw new InvalidOperationException("Empty input sequence");
}
var comparer = Comparer<U>.Default;
T maxItem = items.First();
U maxValue = selector(maxItem);
foreach (T item in items.Skip(1))
{
// Get the value of the item and compare it to the current max.
U value = selector(item);
if (comparer.Compare(value, maxValue) > 0)
{
maxValue = value;
maxItem = item;
}
}
return maxItem;
}
public static T MinBy<T, U>(this IEnumerable<T> items, Func<T, U> selector)
{
if (!items.Any())
{
throw new InvalidOperationException("Empty input sequence");
}
var comparer = Comparer<U>.Default;
T maxItem = items.First();
U maxValue = selector(maxItem);
foreach (T item in items.Skip(1))
{
// Get the value of the item and compare it to the current max.
U value = selector(item);
if (comparer.Compare(value, maxValue) < 0)
{
maxValue = value;
maxItem = item;
}
}
return maxItem;
}
}
// https://github.com/Burtsev-Alexey/net-object-deep-copy

View file

@ -322,8 +322,8 @@ public abstract class EquippableItemData : CraftedItemData
// [InspectableField, JsonProperty("heatExponent"), Key(16), SimplePerformanceStat]
// public PerformanceStat HeatExponent = new PerformanceStat();
[InspectableAnimationCurve, JsonProperty("performanceCurve"), Key(17)]
public float4[] HeatPerformanceCurve;
[InspectableAnimationCurve, JsonProperty("heatCurve"), Key(17)]
public BezierCurve HeatPerformanceCurve;
[Inspectable, JsonProperty("resilience"), Key(18)]
public float ThermalResilience = 1;
@ -553,9 +553,4 @@ public class PersonalityAttribute : DatabaseEntry, INamedEntry
get => Name;
set => Name = value;
}
}
public interface ITintInspector
{
float3 TintColor { get; }
}