Skip to content

Instantly share code, notes, and snippets.

@shanecelis
Last active November 25, 2017 20:36
Show Gist options
  • Save shanecelis/e5d76ead850df257f11a679920a5d851 to your computer and use it in GitHub Desktop.
Save shanecelis/e5d76ead850df257f11a679920a5d851 to your computer and use it in GitHub Desktop.
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