How to convert callbacks to Rx.Observable? - system.reactive

If an external library offers only to register a callback instead of an event, what is the best way to create an Observable from it?
If it where an event I could use Observable.FromEventPattern but in this case the only idea I have is to use a Subjectand queue events in it on each callback.
Is there any better way to do this?

Use Observable.Create. Here's an example:
void Main()
{
var target = new SampleCallbacker();
var actionB = new Action<int>(i => Console.WriteLine($"{i} * {i} = {i * i}."));
target.Register(actionB);
var observable = Observable.Create<int>(observer =>
{
var action = new Action<int>(i => observer.OnNext(i));
target.Register(action);
return () => target.Unregister(action);
});
var subscription = observable.Subscribe(i => Console.WriteLine($"From observable: {i} was fired."));
target.Fire(1);
target.Fire(2);
target.Fire(3);
Console.WriteLine("Unsusbscribing observable...");
subscription.Dispose();
target.Fire(4);
target.Fire(5);
}
class SampleCallbacker
{
private List<Action<int>> _actions = new List<System.Action<int>>();
public void Register(Action<int> action)
{
_actions.Add(action);
}
public void Unregister(Action<int> action)
{
while (_actions.Remove(action))
{} //loop remove
}
public void Fire(int i)
{
foreach (var action in _actions)
{
action(i);
}
}
}

Related

How to invoke a listener from another listener?

I'm using in_app_purchase package and I need to convert/map listener which is listening for List<PurchaseDetails> to another listener as shown below:
class Foo {
Foo() {
InAppPurchase.instance.purchaseStream.listen(_listener);
}
void _listener(List<PurchaseDetails> list) {
// How to pass these ids to `addListener()`
final List<String> ids = list.map((e) => e.productID).toList();
}
void addListener(void Function(List<String>) f) {}
}
This is how I want to use my listener
void main() {
Foo().addListener((List<String> ids) {});
}
Despite what your code comment says, I think what you're really asking for is for the internal _listener to invoke the callback that was previously passed as an argument to addListener (and not for _listener to call addListener directly, which it could just do directly).
Just have addListener save the callback to a member variable and let your internal listener invoke that:
class Foo {
void Function(List<String>)? _listener;
Foo() {
InAppPurchase.instance.purchaseStream.listen(_internalListener);
}
void _internalListener(List<PurchaseDetails> list) {
var listener = _listener;
if (listener == null) {
return;
}
final List<String> ids = list.map((e) => e.productID).toList();
listener(ids);
}
void addListener(void Function(List<String>) f) => _listener = f;
}
If you want callers to be able to call addListener multiple times to register multiple callbacks, you would need to store them in a collection (and provide a mechanism to unregister callbacks):
class Foo {
final _listenerMap = <Object, void Function(List<String>)>{};
Foo() {
InAppPurchase.instance.purchaseStream.listen(_internalListener);
}
void _internalListener(List<PurchaseDetails> list) {
if (_listenerMap.isEmpty) {
return;
}
final List<String> ids = list.map((e) => e.productID).toList();
for (var listener in _listenerMap.values) {
listener(ids);
}
}
Object addListener(void Function(List<String>) f) {
var token = Object();
_listenerMap[token] = f;
return token;
}
void removeListener(Object token) {
_listenerMap.remove(token);
}
}

How to get the input action which is performing?

I'm learning the new input system and I'm looking for something like this:
if(anyInputActionIsPerformed)
{
return thatInputAction;
}
What I'm trying to do is to get the name of the current input action so I can save it in my input buffer for triggering something later.
You can use IsPressed directly:
_controls.ActionMap.Action.IsPressed()
For Example:
private PlayerControls _controls;
public void Start()
{
_controls = new PlayerControls();
_controls.Enable();
}
void Update()
{
if (_controls.GamePlay.TakeItem.IsPressed()) Debug.Log("Is Pressed!");
}
Single Action Map Event Triggered
Controls.GamePlay.Get().actionTriggered += ctx => Debug.Log(ctx.action);
Single Action Map Direct Triggered
foreach (var _inputAction in Controls.GamePlay.Get().actions.Where(_inputAction => _inputAction.triggered))
{
Debug.Log(_inputAction);
}
Total Action Map Triggered
foreach (var _inputAction in Controls.asset.actionMaps.SelectMany(_assetActionMap => _assetActionMap.actions.Where(_inputAction => _inputAction.triggered)))
{
Debug.Log(_inputAction);
}

Delete all the Hangfire jobs

At startup, I try to delete all the existing hangfire jobs, but none of the methods I use seem to work.
Notice that I use a MongoDB database, but not sure it is related to the issue.
Here is how I add my job:
RecurringJob.AddOrUpdate<IPostIndexerJob>(j => j.Execute(), configuration.GetValue<string>("Job:Interval"));
Here is my code, if anyone has an idea that help?
static private void ScheduleJobs(IConfiguration configuration)
{
PurgeJobs1();
PurgeJobs2();
PurgeJobs3();
}
static private void PurgeJobs1()
{
var mon = JobStorage.Current.GetMonitoringApi();
mon.EnqueuedJobs("socloze-elasticsearch-post-indexer", 0, 99999999).ForEach(x =>
{
BackgroundJob.Delete(x.Key);
});
}
static private void PurgeJobs2()
{
var monitor = JobStorage.Current.GetMonitoringApi();
foreach (var queue in monitor.Queues())
{
PurgeQueue(queue.Name);
}
}
static private void PurgeJobs3()
{
// Clean
foreach (var item in Hangfire.JobStorage.Current.GetConnection().GetRecurringJobs())
RecurringJob.RemoveIfExists(item.Id);
}
static private void PurgeQueue(string queueName)
{
var toDelete = new List<string>();
var monitor = JobStorage.Current.GetMonitoringApi();
var queue = monitor.Queues().First(x => x.Name == queueName);
for (var i = 0; i < System.Math.Ceiling(queue.Length / 1000d); i++)
{
monitor.EnqueuedJobs(queue.Name, 1000 * i, 1000)
.ForEach(x => toDelete.Add(x.Key));
}
foreach (var jobId in toDelete)
{
BackgroundJob.Delete(jobId);
}
}
To delete recurring job (what i think you meant) you should firstly ask your storage to provide you all recurring jobs you have set:
jobStorage.GetConnection().GetRecurringJobs(); // where jobStorage is your storage instance, you can access it via JobStorage.Current in static context.
then delete a desired entry:
recurringJobManager.RemoveIfExists("someId"); // where recurringJobManager is instance of IRecurringJobManager
then PurgeJobs3() should work as expected (as it uses IRecurringJobManager under the hood). Perhaps the job identifier does not match your target?

Rx.Net: Chaining subscribers - alternative approach?

How can I re-write this code so that I don't have to chain Subscribers like below? Reason for asking is, this style will limit in an observable depending on another observable due to the style of the code, it can get confusing.
var results = myService
.GetData(accountId) // returns IObservable
.Subscribe(data =>
{
new MyWork().Execute(data) // returns IObservable
.Subscribe(result =>
{
myResults.Add(result);
WriteLine($"Result Id: {result.Id}");
WriteLine($"Result Status: {result.Pass}");
});
});
Added after 1st reply from Peter Bons
Below is the code for MyWork class that has the Execute Method
public class MyWork
{
public virtual IObservable<MyResult> Execute(MyData data)
{
MyResult result = null;
return IsMatch(data)
.Do(isMatch =>
{
if (isMatch)
{
result = new MyResult(1, true);
}
})
.Select(_ => result);
}
public IObservable<bool> IsMatch(MyData data)
{
return true;
}
}
It's really quite simple.
var results =
myService
.GetData(accountId)
.SelectMany(data => new MyWork().Execute(data))
.Subscribe(result =>
{
myResults.Add(result);
Console.WriteLine($"Result Id: {result.Id}");
Console.WriteLine($"Result Status: {result.Pass}");
});
If ever you are subscribing within a subscription then you are doing something wrong. Keep that in mind. There is almost always a way to make it a pure query with a single subscription.
Just to help out with testing, here's the code required to make this a Minimal, Complete, and Verifiable example.
public static class myService
{
public static IObservable<MyData> GetData(int x)
=> Observable.Return(new MyData());
}
public class MyWork
{
public virtual IObservable<MyResult> Execute(MyData data)
{
MyResult result = null;
return IsMatch(data)
.Do(isMatch =>
{
if (isMatch)
{
result = new MyResult() { Id = 1, Pass = true};
}
})
.Select(_ => result);
}
public IObservable<bool> IsMatch(MyData data)
{
return Observable.Return(true);
}
}
public class MyResult
{
public int Id;
public bool Pass;
}
public class MyData { }

Reactive Extension in msword

I am wondering if it is possible to use Reactive Extensions in Word. I have seen this where Jeff shows how to wire up a workbook open event in excel http://social.msdn.microsoft.com/Forums/en/rx/thread/5ace45b1-778b-4ddd-b2ab-d5c8a1659f5f.
I wondering if I could do the same sort of thing in word.
I have got this far....
public static class ApplicationExtensions
{
public static IObservable<Word.Document> DocumentBeforeSaveAsObservable(this Word.Application application)
{
return Observable.Create<Word.Document>(observer =>
{
Word.ApplicationEvents4_DocumentBeforeSaveEventHandler handler = observer.OnNext;
application.DocumentBeforeSave += handler;
return () => application.DocumentBeforeSave -= handler;
});
}
}
but I receive the error No overload for 'OnNext' matches delegate 'Microsoft.Office.Interop.Word.ApplicationEvents4_DocumentBeforeSaveEventHandler
Can anyone point me in the right direction.
Regards
Mike
Your problem is an issue of delegate signatures.
IObserver<T>.OnNext is defined as void (T value)
whereas ApplicationEvents4_DocumentBeforeSaveEventHandler is defined as void (Document doc, ref bool SaveAsUI, ref bool Cancel)
If you only need to emit the Document (and not the other details, like making it cancelable), you can do something like this:
public static IObservable<Word.Document> DocumentBeforeSaveAsObservable(
this Word.Application application)
{
return Observable.Create<Word.Document>(observer =>
{
Word.ApplicationEvents4_DocumentBeforeSaveEventHandler handler =
(doc, ref saveAsUI, ref cancel) => observer.OnNext(doc);
application.DocumentBeforeSave += handler;
return () => application.DocumentBeforeSave -= handler;
});
}
If you do require all the data, you'll need to create a wrapper class of some kind an IObservable sequence can only emit a single type:
public class DocumentBeforeSaveEventArgs : CancelEventArgs
{
public Document Document { get; private set; }
public bool SaveAsUI { get; private set; }
public DocumentBeforeSaveEventArgs(Document document, bool saveAsUI)
{
this.Document = document;
this.SaveAsUI = saveAsUI;
}
}
And then you can use it like so:
public static IObservable<Word.Document> DocumentBeforeSaveAsObservable(
this Word.Application application)
{
return Observable.Create<Word.Document>(observer =>
{
Word.ApplicationEvents4_DocumentBeforeSaveEventHandler handler =
(doc, ref saveAsUI, ref cancel) =>
{
var args = new DocumentBeforeSaveEventArgs(doc, saveAsUI);
observer.OnNext(args);
cancel = args.Cancel;
};
application.DocumentBeforeSave += handler;
return () => application.DocumentBeforeSave -= handler;
});
}