Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improving ThreadingHost #20

Open
guerro323 opened this issue Jul 8, 2020 · 2 comments
Open

Improving ThreadingHost #20

guerro323 opened this issue Jul 8, 2020 · 2 comments
Assignees
Labels
enhancement New feature or request

Comments

@guerro323
Copy link
Owner

Problems

Right now there are some problems about how ThreadingHost work:

  • You need to implement an update method for updating on a created thread Listen() and on an existing thread ListenOnThread(), which can be seen as problematic on MainThreadHost (since that for now it can only be updated if it was called via ListenOnThread), and this is also problematic for applications that could be fully run on existing threads...
  • ThreadingHost is somewhat a singleton, you can have only one instance, which isn't really problematic imo, but if you have want to have one server and one client simulation, you'll need to inject two Instance, and if you want to have them multithreaded or different update frequency is impossible for now.

Proposed solution

  • Remove all listening functions in ThreadingHost and remove all static variables.
  • Add a new Listen-type method called Start(ThreadUpdaterBase, IKey[])

The ThreadUpdaterBase will possess a way to update applications via a method calleed Update() and can contain multiple applications of the same type (which can be retrieved via a key).

Example:

ThreadUpdaterBase mainThreadUpdater;
ThreadUpdaterBase simulationThreadUpdater;

void Init()
{
    mainThreadUpdater = new ThreadUpdater(Thread.CurrentThread);

    var mainThreadHost = new MainThreadHost();
    mainThreadHost.Start(mainThreadUpdater, new PrincipalHostKey<MainThreadHost>());

    // By default, this method will automatically call for us ThreadUpdaterBase.Update()
    simulationThreadUpdater = ThreadUpdaterHelper.FromNewThread(thread => new ThreadUpdater(thread), default(CancellationToken));

    var clientSimuHost = new SimulationThreadHost();
    clientSimuHost.Start(simulationThreadUpdater, new ClientSimulationKey());
    var serverSimuHost = new SimulationThreadHost();
    serverSimuHost.Start(simulationThreadUpdater, new ServerSimulationKey(), new PrincipalHostKey<ServerSimulationKey>());

    Debug.Assert(mainThreadUpdater.GetListener<PrincipalHostKey<MainThreadHost>, MainThreadHost>() == mainThreadHost);
}

void Update()
{
    // Since mainThreadUpdater is in the same thread as where this method is called and that we manually created it, we need to manually call mainThreadUpdater
    if (mainThreadUpdater.IsSameThread)
        mainThreadUpdater.Update();

    // Synchronizing a ThreadUpdater that exist in another thread (aka just locking)
    using (simulationThreadUpdater.SynchronizeThread()) {
        // Synchronized!
    }

    // Or maybe just scheduling some actions?
    if (SimulationThreadHost is IScheduler scheduler) {
        var StartTick = Environment.Tickcount;
        scheduler.Schedule(() => System.Console.WriteLine($"Hello world! {Environment.Tickcount} - {StartTick}"));
    }
}

class ThreadUpdater : ThreadUpdaterBase, IScheduler {
    private List<IListener> listeners;

    protected override void OnListenerAdded (IListener listener) {
        listener.OnConfirmAdd(this);
    }

    protected override void OnUpdate() {
        listeners.ForEach(listener => listener.Update());
    }

    // implementing some schedulers actions....
}

class PrincipalHostKey<T> : IKey {
    public void ThrowIfNotValid(IDictionary<IKey, object> map, object toInsert) {
        if (toInsert.GetType() != typeof(T))
            throw new Exception();

        foreach (var key in map.Keys) {
            if (key == typeof(PrincipalHostKey<T>))
                throw new Exception($"A principal '{typeof(T)}' host has already been found in the map");
        }
    }
}
@guerro323 guerro323 added the enhancement New feature or request label Jul 8, 2020
@guerro323 guerro323 self-assigned this Jul 8, 2020
@guerro323
Copy link
Owner Author

This is now completed and will be pushed to the next version of GameHost. But there is a now a preferred format to call this, which is more ECS friendly

var world = global.World;

var mainThreadUpdater = world.CreateEntity();
mainThreadUpdater.Set<ListenerCollectionBase>(new ListenerCollection());

var otherThreadUpdater = world.CreateEntity();
otherThreadUpdater.Set<ListenerCollectionBase>(new ThreadListenerCollection(ccsource.Token));

var mainAppEntity = world.CreateEntity(); // will be called on this thread (aka where mainThreadUpdater exist)
mainAppEntity.Set<IListener>(new MainOpApplication());
mainAppEntity.Set(new ListenerCollectionTarget(mainThreadUpdater));

var onOtherThreadAppEntity = world.CreateEntity(); // will be called on another thread
onOtherThreadAppEntity.Set<IListener>(new MainOpApplication());
onOtherThreadAppEntity.Set(new ListenerCollectionTarget(otherThreadUpdater));

while (!Environment.HasShutdownStarted)
{
	global.Loop();
}

ccsource.Cancel();

@guerro323
Copy link
Owner Author

also an important feature that got added (but will be used rarely) is for applications being able to switch current thread on the fly. Since it's done in the background it shouldn't cause any issue. This can be interesting for benchmarking or when an application enable a feature that need to be executed on a special thread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant