using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Esiur.Data; using System.Runtime.CompilerServices; using System.Reflection; using Esiur.Net.IIP; using Esiur.Misc; using Esiur.Security.Permissions; using Esiur.Resource.Template; namespace Esiur.Resource { public class Instance { string name; AutoList children;// = new AutoList(); IResource resource; IStore store; AutoList parents;// = new AutoList(); bool inherit; ResourceTemplate template; AutoList managers;// = new AutoList(); public delegate void ResourceModifiedEvent(IResource resource, string propertyName, object newValue, object oldValue); public delegate void ResourceEventOccurredEvent(IResource resource, string eventName, string[] receivers, object[] args); public delegate void ResourceDestroyedEvent(IResource resource); public event ResourceModifiedEvent ResourceModified; public event ResourceEventOccurredEvent ResourceEventOccured; public event ResourceDestroyedEvent ResourceDestroyed; KeyList attributes = new KeyList(); List ages = new List(); private uint age; uint id; /// /// Instance attributes are custom properties associated with the instance, a place to store information by IStore. /// public KeyList Attributes { get { return attributes; } } /// /// Get the age of a given property index. /// /// Zero-based property index. /// Age. public uint GetAge(byte index) { if (index < ages.Count) return ages[index]; else return 0; } /// /// Age of the instance, increments by 1 in every modification. /// public uint Age { get { return age; } internal set { age = value; } } /// /// Instance Id. /// public uint Id { get { return id; } } /// /// Import properties from bytes array. /// /// /// public bool Deserialize(object[] properties) { foreach (var pt in template.Properties) { #if NETSTANDARD1_5 var pi = resource.GetType().GetTypeInfo().GetProperty(pt.Name); #else var pi = resource.GetType().GetProperty(pt.Name); #endif if (!(properties[pt.Index] is NotModified)) pi.SetValue(resource, properties[pt.Index]); } return true; } /// /// Export all properties with ResourceProperty attributed as bytes array. /// /// public object[] Serialize() { List props = new List(); foreach (var pt in template.Properties) { #if NETSTANDARD1_5 var pi = resource.GetType().GetTypeInfo().GetProperty(pt.Name); #else var pi = resource.GetType().GetProperty(pt.Name); #endif var rt = pi.GetValue(resource, null); props.Add(rt); } return props.ToArray(); } /* public bool Deserialize(byte[] data, uint offset, uint length) { var props = Codec.ParseValues(data, offset, length); Deserialize(props); return true; } */ /* public byte[] Serialize(bool includeLength = false, DistributedConnection sender = null) { //var bl = new BinaryList(); List props = new List(); foreach (var pt in template.Properties) { var pi = resource.GetType().GetProperty(pt.Name); var rt = pi.GetValue(resource, null); // this is a cool hack to let the property know the sender if (rt is Func) rt = (rt as Func)(sender); props.Add(rt); } if (includeLength) { return Codec.Compose(props.ToArray(), false); } else { var rt = Codec.Compose(props.ToArray(), false); return DC.Clip(rt, 4, (uint)(rt.Length - 4)); } } public byte[] StorageSerialize() { var props = new List(); foreach(var pt in template.Properties) { if (!pt.Storable) continue; var pi = resource.GetType().GetProperty(pt.Name); if (!pi.CanWrite) continue; var rt = pi.GetValue(resource, null); props.Add(rt); } return Codec.Compose(props.ToArray(), false); } */ /// /// If True, the instance can be stored to disk. /// /// public bool IsStorable() { #if NETSTANDARD1_5 var attrs = resource.GetType().GetTypeInfo().GetCustomAttributes(typeof(Storable), true).ToArray(); #else var attrs = resource.GetType().GetCustomAttributes(typeof(Storable), true); #endif return attrs.Length > 0; } /// /// Notify listeners that a property was modified. /// /// /// /// public void Modified([CallerMemberName] string propertyName = "", object newValue = null, object oldValue = null) { if (newValue == null) { object val; if (GetPropertyValue(propertyName, out val)) ResourceModified?.Invoke(resource, propertyName, val, oldValue); } else ResourceModified?.Invoke(resource, propertyName, newValue, oldValue); } internal void EmitResourceEvent(string name, string[] receivers, object[] args) { ResourceEventOccured?.Invoke(resource, name, receivers, args); } /// /// Get the value of a given property by name. /// /// Property name /// Output value /// True, if the resource has the property. public bool GetPropertyValue(string name, out object value) { #if NETSTANDARD1_5 PropertyInfo pi = resource.GetType().GetTypeInfo().GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); #else PropertyInfo pi = resource.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); #endif if (pi != null) { #if NETSTANDARD1_5 object[] ca = pi.GetCustomAttributes(typeof(ResourceProperty), false).ToArray(); #else object[] ca = pi.GetCustomAttributes(typeof(ResourceProperty), false); #endif if (ca.Length > 0) { value = pi.GetValue(resource, null); //if (value is Func) // value = (value as Func)(sender); return true; } } value = null; return false; } public bool Inherit { get { return inherit; } } /// /// List of parents. /// public AutoList Parents { get { return parents; } } /// /// Store responsible for creating and keeping the resource. /// public IStore Store { get { return store; } } /// /// List of children. /// public AutoList Children { get { return children; } } /// /// The unique and permanent link to the resource. /// public string Link { get { if (this.store != null) return this.store.Link(this.resource); else { var l = new List(); //l.Add(name); var p = this.resource; // parents.First(); while (true) { l.Insert(0, p.Instance.name); if (p.Instance.parents.Count == 0) break; p = p.Instance.parents.First(); } return String.Join("/", l.ToArray()); } } } /// /// Instance name. /// public string Name { get { return name; } } /// /// Resource managed by this instance. /// public IResource Resource { get { return resource; } } /// /// Resource template describes the properties, functions and events of the resource. /// public ResourceTemplate Template { get { return template; } } /// /// Create new instance. /// /// Instance Id. /// Name of the instance. /// Resource to manage. /// Store responsible for the resource. public Instance(uint id, string name, IResource resource, IStore store) { this.store = store; this.resource = resource; this.id = id; this.name = name; children = new AutoList(this); parents = new AutoList(this); managers = new AutoList(this); children.OnAdd += Children_OnAdd; children.OnRemoved += Children_OnRemoved; resource.OnDestroy += Resource_OnDestroy; template = Warehouse.GetTemplate(resource.GetType()); // set ages for (byte i = 0; i < template.Properties.Length; i++) ages.Add(0); // connect events Type t = resource.GetType(); #if NETSTANDARD1_5 var events = t.GetTypeInfo().GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); #else var events = t.GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); #endif foreach (var evt in events) { if (evt.EventHandlerType != typeof(ResourceEventHanlder)) continue; var ca = (ResourceEvent[])evt.GetCustomAttributes(typeof(ResourceEvent), true); if (ca.Length == 0) continue; ResourceEventHanlder proxyDelegate = (receivers, args) => EmitResourceEvent(evt.Name, receivers, args); evt.AddEventHandler(resource, proxyDelegate); } } private void Children_OnRemoved(Instance parent, IResource value) { value.Instance.parents.Remove(resource); } private void Children_OnAdd(Instance parent, IResource value) { value.Instance.parents.Add(resource); } private void Resource_OnDestroy(object sender) { ResourceDestroyed?.Invoke((IResource)sender); } } }