Skip to content

Commit

Permalink
Merge pull request #2454 from chkn/events
Browse files Browse the repository at this point in the history
Fix events issues and implement FSharpEvent`2
  • Loading branch information
alfonsogarciacaro authored May 27, 2021
2 parents 0633c4c + 122c67e commit aae71ec
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 148 deletions.
9 changes: 8 additions & 1 deletion src/Fable.Transforms/FSharp2Fable.Util.fs
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,14 @@ module Patterns =
Lambda(_callback, NewDelegate(_, Lambda(_delegateArg0, Lambda(_delegateArg1, Application(Value _callback',[],[Value _delegateArg0'; Value _delegateArg1'])))))])
when createEvent.FullName = Types.createEvent ->
let eventName = addEvent.CompiledName.Replace("add_","")
Some (callee, eventName)
match addEvent.DeclaringEntity with
| Some klass ->
klass.MembersFunctionsAndValues
|> Seq.tryFind (fun m -> m.LogicalName = eventName)
|> function
| Some memb -> Some (callee, memb)
| _ -> None
| _ -> None
| _ -> None

let (|ConstructorCall|_|) = function
Expand Down
10 changes: 6 additions & 4 deletions src/Fable.Transforms/FSharp2Fable.fs
Original file line number Diff line number Diff line change
Expand Up @@ -493,9 +493,10 @@ let private transformExpr (com: IFableCompiler) (ctx: Context) fsExpr =

| FSharpExprPatterns.Let((var, value), body) ->
match value, body with
| CreateEvent(value, eventName), _ ->
| (CreateEvent(value, event) as createEvent), _ ->
let! value = transformExpr com ctx value
let value = get None Fable.Any value eventName
let typ = makeType ctx.GenericArgs createEvent.Type
let value = makeCallFrom com ctx (makeRangeFrom createEvent) typ Seq.empty (Some value) [] event
let ctx, ident = putBindingInScope com ctx var value
let! body = transformExpr com ctx body
return Fable.Let(ident, value, body)
Expand Down Expand Up @@ -563,10 +564,11 @@ let private transformExpr (com: IFableCompiler) (ctx: Context) fsExpr =

| FSharpExprPatterns.CallWithWitnesses(callee, memb, ownerGenArgs, membGenArgs, witnesses, args) ->
match callee with
| Some(CreateEvent(callee, eventName)) ->
| Some(CreateEvent(callee, event) as createEvent) ->
let! callee = transformExpr com ctx callee
let typ = makeType ctx.GenericArgs createEvent.Type
let callee = makeCallFrom com ctx (makeRangeFrom createEvent) typ Seq.empty (Some callee) [] event
let! args = transformExprList com ctx args
let callee = get None Fable.Any callee eventName
let genArgs = ownerGenArgs @ membGenArgs |> Seq.map (makeType ctx.GenericArgs)
let typ = makeType ctx.GenericArgs fsExpr.Type
return makeCallFrom com ctx (makeRangeFrom fsExpr) typ genArgs (Some callee) args memb
Expand Down
3 changes: 3 additions & 0 deletions src/Fable.Transforms/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1723,6 +1723,8 @@ let stringModule (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr opti
let seqModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) =
match i.CompiledName, args with
| "Cast", [arg] -> Some arg // Erase
| "CreateEvent", [addHandler; removeHandler; createHandler] ->
Helper.LibCall(com, "Event", "createEvent", t, [addHandler; removeHandler], i.SignatureArgTypes, ?loc=r) |> Some
| ("Distinct" | "DistinctBy" | "Except" | "GroupBy" | "CountBy" as meth), args ->
let meth = Naming.lowerFirst meth
let args = injectArg com ctx r "Seq2" meth i.GenericArgs args
Expand Down Expand Up @@ -3170,6 +3172,7 @@ let private replacedModules =
"Microsoft.FSharp.Control.LazyExtensions", laziness
"Microsoft.FSharp.Control.CommonExtensions", controlExtensions
"Microsoft.FSharp.Control.FSharpEvent`1", events
"Microsoft.FSharp.Control.FSharpEvent`2", events
"Microsoft.FSharp.Control.EventModule", events
"Microsoft.FSharp.Control.ObservableModule", observable
Types.type_, types
Expand Down
207 changes: 72 additions & 135 deletions src/fable-library/Event.ts
Original file line number Diff line number Diff line change
@@ -1,213 +1,136 @@
import { IObservable, IObserver, Observer, protect } from "./Observable.js";
import { IObservable, IObserver, Observer } from "./Observable.js";
import { Option, some, value } from "./Option.js";
import { FSharpChoice$2, Choice_tryValueIfChoice1Of2, Choice_tryValueIfChoice2Of2 } from "./Choice.js";
import { IDisposable } from "./Util.js";

export type Delegate<T> = (x: T) => void;
export type DotNetDelegate<T> = (sender: any, x: T) => void;
type EventDelegate<T> = Delegate<T> | DotNetDelegate<T>;

export interface IDelegateEvent<T> {
AddHandler(d: DotNetDelegate<T>): void;
RemoveHandler(d: DotNetDelegate<T>): void;
}

export interface IEvent<T> extends IObservable<T>, IDelegateEvent<T> {
Publish: IEvent<T>;
Trigger(x: T): void;
}

export class Event<T> implements IEvent<T> {
public delegates: Delegate<T>[];
private _subscriber?: (o: IObserver<T>) => IDisposable;
private _dotnetDelegates?: Map<DotNetDelegate<T>, Delegate<T>>;
private delegates: EventDelegate<T>[];

constructor(_subscriber?: (o: IObserver<T>) => IDisposable, delegates?: any[]) {
this._subscriber = _subscriber;
this.delegates = delegates || new Array<Delegate<T>>();
constructor() {
this.delegates = [];
}

public Add(f: Delegate<T>) {
this._addHandler(f);
}

// IEvent<T> methods

get Publish() {
return this;
}

public Trigger(value: T) {
this.delegates.forEach((f) => f(value));
public Trigger(value: T): void;
public Trigger(sender: any, value: T): void;
public Trigger(senderOrValue: any, valueOrUndefined?: T) {
let sender: any;
let value : T;
if (valueOrUndefined === undefined) {
sender = null;
value = senderOrValue;
} else {
sender = senderOrValue;
value = valueOrUndefined;
}
this.delegates.forEach((f) => f.length === 1 ? (f as Delegate<T>)(value) : f(sender, value));
}

// IDelegateEvent<T> methods

public AddHandler(handler: DotNetDelegate<T>) {
if (this._dotnetDelegates == null) {
this._dotnetDelegates = new Map<DotNetDelegate<T>, Delegate<T>>();
}
const f = (x: T) => handler(null, x);
this._dotnetDelegates.set(handler, f);
this._addHandler(f);
this._addHandler(handler);
}

public RemoveHandler(handler: DotNetDelegate<T>) {
if (this._dotnetDelegates != null) {
const f = this._dotnetDelegates.get(handler);
if (f != null) {
this._dotnetDelegates.delete(handler);
this._removeHandler(f);
}
}
this._removeHandler(handler);
}

// IObservable<T> methods

public Subscribe(arg: IObserver<T> | Delegate<T>) {
return typeof arg === "function"
? this._subscribeFromCallback(arg as Delegate<T>)
: this._subscribeFromObserver(arg as IObserver<T>);
const callback = typeof arg === "function"
? arg as Delegate<T>
: (arg as IObserver<T>).OnNext;
this._addHandler(callback);
return { Dispose: () => { this._removeHandler(callback); } };
}

private _addHandler(f: Delegate<T>) {
private _addHandler(f: EventDelegate<T>) {
this.delegates.push(f);
}

private _removeHandler(f: Delegate<T>) {
private _removeHandler(f: EventDelegate<T>) {
const index = this.delegates.indexOf(f);
if (index > -1) {
this.delegates.splice(index, 1);
}
}

private _subscribeFromObserver(observer: IObserver<T>): IDisposable {
if (this._subscriber) {
return this._subscriber(observer);
}

const callback = observer.OnNext;
this._addHandler(callback);
return { Dispose: () => { this._removeHandler(callback); } };
}

private _subscribeFromCallback(callback: Delegate<T>): IDisposable {
this._addHandler(callback);
return { Dispose: () => { this._removeHandler(callback); } };
}
}

export function add<T>(callback: (x: T) => void, sourceEvent: IEvent<T>) {
(sourceEvent as Event<T>).Subscribe(new Observer(callback));
if (sourceEvent instanceof Event) {
sourceEvent.Add(callback);
} else {
sourceEvent.Subscribe(new Observer(callback));
}
}

export function choose<T, U>(chooser: (x: T) => Option<U>, sourceEvent: IEvent<T>) {
const source = sourceEvent as Event<T>;
return new Event<U>((observer) =>
source.Subscribe(new Observer<T>((t) =>
protect(
() => chooser(t),
(u) => { if (u != null) { observer.OnNext(value(u)); } },
observer.OnError),
observer.OnError, observer.OnCompleted)),
source.delegates);
const ev = new Event<U>();
add((t) => {
const u = chooser(t);
if (u != null) { ev.Trigger(value(u)); }
}, sourceEvent);
return ev;
}

export function filter<T>(predicate: (x: T) => boolean, sourceEvent: IEvent<T>) {
return choose((x) => predicate(x) ? some(x) : undefined, sourceEvent);
}

export function map<T, U>(mapping: (x: T) => U, sourceEvent: IEvent<T>) {
const source = sourceEvent as Event<T>;
return new Event<U>((observer) =>
source.Subscribe(new Observer<T>((t) =>
protect(
() => mapping(t),
observer.OnNext,
observer.OnError),
observer.OnError, observer.OnCompleted)),
source.delegates);
const ev = new Event<U>();
add((t) => ev.Trigger(mapping(t)), sourceEvent);
return ev;
}

export function merge<T>(event1: IEvent<T>, event2: IEvent<T>) {
const source1 = event1 as Event<T>;
const source2 = event2 as Event<T>;
return new Event<T>((observer) => {
let stopped = false;
let completed1 = false;
let completed2 = false;

const h1 = source1.Subscribe(new Observer<T>(
(v) => { if (!stopped) { observer.OnNext(v); } },
(e) => {
if (!stopped) {
stopped = true;
observer.OnError(e);
}
},
() => {
if (!stopped) {
completed1 = true;
if (completed2) {
stopped = true;
observer.OnCompleted();
}
}
}));

const h2 = source2.Subscribe(new Observer<T>(
(v) => { if (!stopped) { observer.OnNext(v); } },
(e) => {
if (!stopped) {
stopped = true;
observer.OnError(e);
}
},
() => {
if (!stopped) {
completed2 = true;
if (completed1) {
stopped = true;
observer.OnCompleted();
}
}
}));

return {
Dispose() {
h1.Dispose();
h2.Dispose();
},
} as IDisposable;
}, source1.delegates.concat(source2.delegates));
const ev = new Event<T>();
const fn = (x: T) => ev.Trigger(x);
add(fn, event1);
add(fn, event2);
return ev;
}

export function pairwise<T>(sourceEvent: IEvent<T>) {
const source = sourceEvent as Event<T>;
return new Event<[T, T]>((observer) => {
let last: T;
return source.Subscribe(new Observer<T>((next) => {
if (last != null) {
observer.OnNext([last, next]);
}
last = next;
}, observer.OnError, observer.OnCompleted));
}, source.delegates);
const ev = new Event<[T, T]>();
let last: T;
let haveLast = false;
add((next) => {
if (haveLast) {
ev.Trigger([last, next]);
}
last = next;
haveLast = true;
}, sourceEvent);
return ev;
}

export function partition<T>(predicate: (x: T) => boolean, sourceEvent: IEvent<T>): [IEvent<T>, IEvent<T>] {
return [filter(predicate, sourceEvent), filter((x) => !predicate(x), sourceEvent)];
}

export function scan<U, T>(collector: (u: U, t: T) => U, state: U, sourceEvent: IEvent<T>) {
const source = sourceEvent as Event<T>;
return new Event<U>((observer) => {
return source.Subscribe(new Observer<T>((t) => {
protect(
() => collector(state, t),
(u) => { state = u; observer.OnNext(u); },
observer.OnError);
}, observer.OnError, observer.OnCompleted));
}, source.delegates);
return map((t) => state = collector(state, t), sourceEvent);
}

export function split<T, U1, U2>(splitter: (x: T) => FSharpChoice$2<U1, U2>, sourceEvent: IEvent<T>): [IEvent<U1>, IEvent<U2>] {
Expand All @@ -217,4 +140,18 @@ export function split<T, U1, U2>(splitter: (x: T) => FSharpChoice$2<U1, U2>, sou
];
}

export function createEvent<T>(addHandler: (h: DotNetDelegate<T>) => void, removeHandler: (h: DotNetDelegate<T>) => void): IEvent<T> {
return {
AddHandler(h) { addHandler(h); },
RemoveHandler(h) { removeHandler(h); },
Subscribe(r) {
const h: DotNetDelegate<T> = (_, args) => r.OnNext(args);
addHandler(h);
return {
Dispose() { removeHandler(h); }
};
}
};
}

export default Event;
2 changes: 1 addition & 1 deletion src/fable-library/Timer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class Timer implements IDisposable {
this._elapsed = new Event<Date>();
}

get Elapsed() {
Elapsed() {
return this._elapsed;
}

Expand Down
Loading

0 comments on commit aae71ec

Please sign in to comment.