0

I have the following interface, it defines a loadable resource that depends on other resources that should be loaded first so it can load itself:

enter image description here

It does its job, the Find method allowing to search for a particular dependency for Load method.

Now I have another problem for which I haven't found an elegant solution for, so far.

Here is an example of what I am trying to model along explanations:

(click to enlarge)

enter image description here

Legend of elements, starting from top:

  • Level represents a game level, it has an abritrary amount of content
  • Sky, Scene and Track are some of the content
    • these can consist of multiple content, e.g. for Scene it is Scene 1 and Scene N
  • the next items represent different file types each content needs
  • the last level, Storage, is where these are to be loaded from

Note: the direction of arrows tells what an object depends on so it can load itself

Here is the implementation I am currently using:

using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Random = UnityEngine.Random;

namespace ZeroAG.WorkInProgress.Resources
{
    public interface IResource
    {
        /// <summary>
        ///     Gets direct dependencies of this instance.
        /// </summary>
        [NotNull]
        IEnumerable<IResource> Dependencies { get; }

        /// <summary>
        ///     Gets the name of this instance.
        /// </summary>
        [NotNull]
        string Name { get; }

        /// <summary>
        ///     Gets any dependencies of this instance that matches a predicate.
        /// </summary>
        [NotNull]
        IEnumerable<T> Find<T>([NotNull] Predicate<T> predicate) where T : IResource;

        /// <summary>
        ///     Loads this instance.
        /// </summary>
        void Load<T>(IResourceProgress<T> progress) where T : IResourceProgressInfo, new();
    }

    public abstract class Resource : IResource
    {
        protected Resource([NotNull] string name, [NotNull] params IResource[] dependencies)
        {
            Name = name ?? throw new ArgumentNullException(nameof(name));
            Dependencies = dependencies ?? throw new ArgumentNullException(nameof(dependencies));
        }

        public IEnumerable<IResource> Dependencies { get; }

        public string Name { get; }

        public IEnumerable<T> Find<T>(Predicate<T> predicate) where T : IResource
        {
            if (predicate == null)
                throw new ArgumentNullException(nameof(predicate));

            var queue = new Queue<IResource>(new[] {this});

            while (queue.Any())
            {
                var dequeue = queue.Dequeue();

                foreach (var dependency in dequeue.Dependencies)
                {
                    queue.Enqueue(dependency);
                }

                if (dequeue is T item && predicate(item))
                {
                    yield return item;
                }
            }
        }

        public virtual void Load<T>(IResourceProgress<T> progress) where T : IResourceProgressInfo, new()
        {
            // just a demo for all derived types

            var count = Random.Range(3, 5);

            for (var i = 0; i < count; i++)
            {
                var value = new T
                {
                    Sender = this,
                    Percentage = 1.0f / (count - 1) * i,
                    Message = $"{GetType().Name}: {i + 1} of {count}"
                };

                progress.Report(value);
            }
        }
    }
}

Problem:

While IResource.Find allows me to find a child dependency, it does not allow me to query for an IResource on same hierachy level or above it, e.g. I need at some point to be able to make queries such as the one depicted in red color, i.e. query about anything from anywhere.

Now, while it is very tempting to add a IResource Parent property to IResource it doesn't really makes sense as there might be multiple parents such as for Scene Atlas.

It seems that I am drifting from a typical tree structure to whatever but I can't identify this structure.

Question:

What am looking for and/or how could IResource be refactored in order to solve this problem?

  • Might I suggest reading Entity Component Systems? I think you will find them interesting. As to your storage problem might I suggest placing storage off to the side and have a dedicated Reader/Writer responsible for reading an Object Graph, then writing that to storage and conversely reading storage and rendering/updating an object graph. If you need this to be dynamic, consider an event system to pass state changes along to the reader/writer to update storage. – Kain0_0 Aug 20 at 1:21
  • Maybe I missed something in your link but I guess my IResource is already more or less composition. I think you've laid out the right term: it's a graph that I have and not a tree; going to dig in this direction. Thanks! – Aybe Aug 20 at 4:45
  • You may want to consider creating several Systems each responsible for a kind of state of representation. You could have a ScreenCoOrds system which knows about every screen co-ordinate. You could trivially ask the system to locate candidates based on that information. Similar systems could manage other information such as labels, colouration, or even just the scenes, key frames, resource, etc... This allows you to quickly perform multi-dimensional queries while still having access to individual objects relatively trivially. – Kain0_0 Aug 20 at 8:53
  • 1
    I think the root cause of the problem is related to the idea that a resource should load itself. The usual, generic OOP advice could lead to that design, however the goal is not to follow generic advice, but to find a workable way to model the actual problem at hand. Perhaps a better way to approach this is to initially treat the whole thing as a data structure that describes the dependencies, but doesn't load anything. Then have some other object process that data structure in a different pass; that way you can have a global search & control where and how things are stored. – Filip Milovanović Aug 20 at 16:51
  • I haven't yet reached that part of the problem but I will try to apply your advice once I get there! – Aybe Aug 22 at 10:06
2

If a node may have multiple parents, it is not a tree structure, it is a directed graph. By introducing the red arrows you now have cycles in the graph, which means you cant really talk of parent-child relationships or hierarchy levels anymore.

Cycles also means you can't just traverse the graph until you find the node matching a predicate, since you might get stuck in an infinite loop.

The simplest solution is to have a collection of all the nodes in the graph, and then iterate through the collection until you find a node which match the predicate.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.