I have an interface IRenderable
and a class that manages rendering all instances of classes implementing the interface.
I have many classes in my code and will expect others to create classes that implement the same interface.
Due to the nature of rendering I want to say things like "Draw instances of this class before instances of this class".
The typical approach is to have every class implement a DrawOrder
property, however I dislike this because classes don't have a definite draw order value, it's the relative order that matters. If I gave every class a DrawOrder
property anyone implementing the interface would need to know what the values of all classes were. Obviously this isn't possible if many people could implement their own class.
What I'd like is to be able to define rules that say "ClassA before ClassB, ClassC before ClassA", then when working out the draw order / adding instances I could infer a correct drawing order. Others implementing the interface could add their own rules relating to built in implementations and their own additions.
EDIT: What I'm hoping for is some class that manages the rules and manages maintaining an order, something like below:
class Renderer
{
private List<Rule> Rules;
private List<IRenderable> Renderables;
// Adds to list of rules
void EnforceBefore(Type FirstClass, Type SecondClass);
// Inserts items ensuring all rules are followed.
void Insert(IRenderable ClassInstance);
void RenderAll();
}
Classes could then add rules as appropriate (or I could have an interface method that returns them).
Below is a quick test that doesn't work
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main(string[] args)
{
List<string> MyList = new List<string> { "wherever", "second", "first", "third", "first", "third", "second" };
RuleBasedComparer<string> RuleComparer = new RuleBasedComparer<string>();
// I want to ensure all instances of "first" appear in the list before all instances of "second"
// and all instances of "second" appear in the list before all instances of "third".
// I don't care where "wherever" appears (or anything beyond the above rules)
RuleComparer.AddRule("first", "second");
RuleComparer.AddRule("second", "third");
MyList.Sort(RuleComparer);
foreach (var item in MyList)
Console.WriteLine(item);
Console.ReadKey();
}
}
public class RuleBasedComparer<T> : Comparer<T>
{
private class OrderRule
{
public readonly T Before;
public readonly T After;
public OrderRule(T before, T after)
{
Before = before;
After = after;
}
}
private List<OrderRule> _Rules = new List<OrderRule>();
public void AddRule(T before, T after)
{
_Rules.Add(new OrderRule(before, after));
}
public override int Compare(T x, T y)
{
// Find the applicable rule for this pair (if any)
var ApplicableRule = _Rules.Where(or => or.After.Equals(x) && or.Before.Equals(y) ||
or.After.Equals(y) && or.Before.Equals(x)).SingleOrDefault();
if (ApplicableRule != null)
{
// If there is a rule then if x should be before y then return -1, otherwise return 1
if (ApplicableRule.Before.Equals(x))
return -1;
else
return 1;
}
else
{
// If no rule exists then say they are equal
return 0;
}
}
}
TL;DR: How do I go from rules that say things like "ClassA before ClassB" to a definite ordering of instances/classes.
Ambiguities created by a lack of complete rules shouldn't matter, I just want the existing rules adhered to.
I've managed to figure out a method that seems to work (complete code example below).
Basically I add my rules, then I assign everything included in a rule with a possible ordering by inserting them into a list one by one, observing the rules as I add each one. The position of the item in the list becomes the ordering. When comparing 2 things in the list (not in a rule) I just return 0.
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main(string[] args)
{
List<string> MyList = new List<string> { "second", "first", "second2", "wherever", "third", "second2", "third", "second", "first" };
RuleBasedComparer<string> RuleComparer = new RuleBasedComparer<string>();
// I want to ensure all instances of "first" appear in the list before all instances of "second" and "second2"
// and all instances of "second" and "second2" appear in the list before all instances of "third".
// I don't care where "wherever" appears (or anything beyond the above rules)
RuleComparer.AddRule("first", "second");
RuleComparer.AddRule("first", "second2");
RuleComparer.AddRule("second", "third");
RuleComparer.AddRule("second2", "third");
MyList.Sort(RuleComparer);
foreach (var item in MyList)
Console.WriteLine(item);
Console.ReadKey();
}
}
public class RuleBasedComparer<T> : Comparer<T>
{
private class OrderRule
{
public readonly T Before;
public readonly T After;
public OrderRule(T before, T after)
{
Before = before;
After = after;
}
}
private List<OrderRule> _Rules = new List<OrderRule>();
private List<T> DesiredOrdering = new List<T>();
private bool _NeedToCalculateOrdering = true;
public void AddRule(T before, T after)
{
if (!_NeedToCalculateOrdering)
throw new InvalidOperationException("Cannot add rules once this comparer has.");
_Rules.Add(new OrderRule(before, after));
}
private void CalculateOrdering()
{
_NeedToCalculateOrdering = false;
var ItemsToOrder = _Rules.SelectMany(r => new[] { r.Before, r.After }).Distinct();
foreach (var ItemToOrder in ItemsToOrder)
{
var MinIndex = 0;
var MaxIndex = DesiredOrdering.Count;
foreach (var Rule in _Rules.Where(r => r.Before.Equals(ItemToOrder)))
{
var indexofAfter = DesiredOrdering.IndexOf(Rule.After);
if (indexofAfter != -1)
{
MaxIndex = Math.Min(MaxIndex, indexofAfter);
}
}
foreach (var Rule in _Rules.Where(r => r.After.Equals(ItemToOrder)))
{
var indexofBefore = DesiredOrdering.IndexOf(Rule.Before);
if (indexofBefore != -1)
{
MinIndex = Math.Max(MinIndex, indexofBefore + 1);
}
}
if (MinIndex > MaxIndex)
throw new InvalidOperationException("Invalid combination of rules found!");
DesiredOrdering.Insert(MinIndex, ItemToOrder);
}
}
public override int Compare(T x, T y)
{
if (_NeedToCalculateOrdering)
CalculateOrdering();
if (x == null && y != null)
{
return -1;
}
else if (x != null && y == null)
return 1;
else if (x == null && y == null)
return 0;
// Find the applicable rule for this pair (if any)
var IndexOfX = DesiredOrdering.IndexOf(x);
var IndexOfY = DesiredOrdering.IndexOf(y);
if (IndexOfX != -1 && IndexOfY != -1)
{
// We have a definite order
if (IndexOfX > IndexOfY)
return 1;
else if (IndexOfX < IndexOfY)
return -1;
else
return 0;
}
else if (IndexOfX != -1)
{
return -1;
}
else if (IndexOfY != -1)
{
return 1;
}
else
{
return 0; // Or maybe compare x to y directly
//return Comparer<T>.Default.Compare(x, y);
}
}
}