Substitute a function deep in the belly of the beast for this proxy when you need to.
/* Original code Copyright (c) 2017 Shane Celis[1] | |
Licensed under the MIT License[2] | |
Original code posted here[3]. | |
This comment generated by code-cite[4]. | |
[1]: https://github.com/shanecelis | |
[2]: https://opensource.org/licenses/MIT | |
[3]: https://gist.github.com/shanecelis/e5d76ead850df257f11a679920a5d851 | |
[4]: https://github.com/shanecelis/code-cite | |
*/ | |
using System; | |
using System.Threading; | |
using System.Collections.Concurrent; | |
/** | |
How many times has this happened to you? You're using a library that provides | |
hooks to do something, let's say, optimize a function's value. The library | |
wants a function, and if your function has one or more of the following | |
properties you're golden. | |
* Your function is pure. | |
* Your function can compute the needed value within its stack frame. | |
* This value is available on demand. | |
But maybe your function isn't pure and you _can't_ stop the world to | |
compute its value. Some times you need to wait for something to happen | |
without halting the main thread. For instance, you might need to let the | |
physics engine run for a few ticks. | |
Sometimes the library calling this function isn't so deeply embedded that you | |
can't just break the library's execution in two: 1. before function | |
evaluation and 2. after function evaluation. It's not the prettiest solution, | |
and it leaks a lot of internals, but it puts you in the drivers seat. | |
If you'd like to maintain the integrity of the library's API and work around | |
this limitation, you can use this little class: QueueProxy. The purpose of | |
this class is to allow you to stick a proxy in your function's stead. Run the | |
library in another thread, and its inputs will be available to some worker | |
thread. | |
``` | |
// Proxy for a function that accepts a int[] and returns a double. | |
var proxy = new QueueProxy<int[], double>(); | |
// Run the optimization algorithm in another thread. | |
Task.Run(() => { | |
optimizer.SetEvaluator((int[] genotype) => proxy.EnqueueAndWait(genotype)); | |
optimizer.Run(); | |
}); | |
``` | |
Then in some way that's conducive to not blocking the world, you can do the | |
work you need to do elsewhere unconstrained by the libraries needs. Read the | |
input. | |
``` | |
int[] genotype; | |
if (proxy.input.TryDequeue(out genotype)) { | |
// Instantiate thing. | |
phenotype = CreatePhenotype(genotype); | |
} | |
``` | |
And write the output when you can. | |
``` | |
if (physics.ticks > 100) { | |
double result = phenotype.position.x; | |
proxy.output.Enqueue(result); | |
} | |
``` | |
Note: This doesn't totally decouple the serial nature of the optimization | |
algorithm, and it in fact relies on it. The optimization, if single | |
threaded, will block waiting for a result. | |
*/ | |
public class QueueProxy<TInput, TOutput> { | |
public ConcurrentQueue<TInput> inputs = new ConcurrentQueue<TInput>(); | |
public ConcurrentQueue<TOutput> outputs = new ConcurrentQueue<TOutput>(); | |
// XXX One could also throw exceptions through the proxy. | |
// public ConcurrentQueue<Exception> error; | |
public TOutput EnqueueAndWait(TInput input) { | |
inputs.Enqueue(input); | |
TOutput result; | |
while (! outputs.TryDequeue(out result)) | |
Thread.Sleep(100); | |
return result; | |
} | |
public bool TryRespond(Func<TInput, TOutput> f) { | |
TInput input; | |
if (inputs.TryDequeue(out input)) { | |
outputs.Enqueue(f(input)); | |
return true; | |
} | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment