| @@ -0,0 +1,33 | |||
|
|
1 | import { ICancellation } from "./interfaces"; | |
|
|
2 | ||
|
|
3 | export class Cancellation implements ICancellation { | |
|
|
4 | isSupported(): boolean { | |
|
|
5 | return false; | |
|
|
6 | } | |
|
|
7 | throwIfRequested(): void { | |
|
|
8 | } | |
|
|
9 | ||
|
|
10 | isRequested(): boolean { | |
|
|
11 | return false; | |
|
|
12 | } | |
|
|
13 | ||
|
|
14 | register(_cb: (e:any) => void): void { | |
|
|
15 | ||
|
|
16 | } | |
|
|
17 | ||
|
|
18 | static readonly none : Cancellation = { | |
|
|
19 | isSupported(): boolean { | |
|
|
20 | return false; | |
|
|
21 | }, | |
|
|
22 | ||
|
|
23 | throwIfRequested(): void { | |
|
|
24 | }, | |
|
|
25 | ||
|
|
26 | isRequested(): boolean { | |
|
|
27 | return false; | |
|
|
28 | }, | |
|
|
29 | ||
|
|
30 | register(_cb: (e:any) => void): void { | |
|
|
31 | } | |
|
|
32 | }; | |
|
|
33 | } No newline at end of file | |
| @@ -0,0 +1,85 | |||
|
|
1 | import { IObservable, IObserver, IDestroyable, ICancellation } from '../interfaces'; | |
|
|
2 | import { Cancellation } from '../Cancellation' | |
|
|
3 | import * as TraceSource from '../log/TraceSource' | |
|
|
4 | import { argumentNotNull } from '../safe'; | |
|
|
5 | ||
|
|
6 | const trace = TraceSource.get('@implab/core/components/Observable'); | |
|
|
7 | ||
|
|
8 | ||
|
|
9 | class Observable<T> implements IObservable<T> { | |
|
|
10 | private _once = new Array<IObserver<T>>(); | |
|
|
11 | ||
|
|
12 | private readonly _observers = new Array<IObserver<T>>(); | |
|
|
13 | ||
|
|
14 | constructor(func: (notify: IObserver<T>) => void) { | |
|
|
15 | argumentNotNull(func, "func"); | |
|
|
16 | ||
|
|
17 | func(this._notify.bind(this)); | |
|
|
18 | } | |
|
|
19 | ||
|
|
20 | on(observer: IObserver<T>): IDestroyable { | |
|
|
21 | argumentNotNull(observer, "observer"); | |
|
|
22 | ||
|
|
23 | this._observers.push(observer); | |
|
|
24 | ||
|
|
25 | let me = this; | |
|
|
26 | return { | |
|
|
27 | destroy() { | |
|
|
28 | me._removeObserver(observer); | |
|
|
29 | } | |
|
|
30 | } | |
|
|
31 | } | |
|
|
32 | ||
|
|
33 | wait(ct: ICancellation = Cancellation.none): Promise<T> { | |
|
|
34 | return new Promise<T>((resolve, reject) => { | |
|
|
35 | this._once.push(resolve); | |
|
|
36 | if (ct.isSupported()) { | |
|
|
37 | ct.register((e) => { | |
|
|
38 | this._removeOnce(resolve); | |
|
|
39 | reject(e); | |
|
|
40 | }); | |
|
|
41 | } | |
|
|
42 | }); | |
|
|
43 | } | |
|
|
44 | ||
|
|
45 | onObserverException(e: any) { | |
|
|
46 | trace.error("Unhandled exception in the observer: {0}", e); | |
|
|
47 | } | |
|
|
48 | ||
|
|
49 | private _removeOnce(d: IObserver<T>) { | |
|
|
50 | let i = this._once.indexOf(d); | |
|
|
51 | if (i >= 0) | |
|
|
52 | this._once.splice(i); | |
|
|
53 | } | |
|
|
54 | ||
|
|
55 | private _removeObserver(d: IObserver<T>) { | |
|
|
56 | let i = this._observers.indexOf(d); | |
|
|
57 | if (i >= 0) | |
|
|
58 | this._observers.splice(i); | |
|
|
59 | } | |
|
|
60 | ||
|
|
61 | private _notify(evt: T) { | |
|
|
62 | let guard = (observer: IObserver<T>) => { | |
|
|
63 | try { | |
|
|
64 | observer(evt); | |
|
|
65 | } catch (e) { | |
|
|
66 | this.onObserverException(e); | |
|
|
67 | } | |
|
|
68 | } | |
|
|
69 | ||
|
|
70 | if (this._once.length) { | |
|
|
71 | for (let i = 0; i < this._once.length; i++) | |
|
|
72 | guard(this._once[i]); | |
|
|
73 | this._once = []; | |
|
|
74 | } | |
|
|
75 | ||
|
|
76 | for (let i = 0; i < this._observers.length; i++) | |
|
|
77 | guard(this._observers[i]); | |
|
|
78 | } | |
|
|
79 | } | |
|
|
80 | ||
|
|
81 | namespace Observable { | |
|
|
82 | export const traceSource = trace; | |
|
|
83 | } | |
|
|
84 | ||
|
|
85 | export = Observable; No newline at end of file | |
| @@ -0,0 +1,83 | |||
|
|
1 | import { watchFile } from "fs"; | |
|
|
2 | ||
|
|
3 | export interface IDestroyable { | |
|
|
4 | destroy(); | |
|
|
5 | } | |
|
|
6 | ||
|
|
7 | export interface ICancellation { | |
|
|
8 | throwIfRequested(): void; | |
|
|
9 | isRequested(): boolean; | |
|
|
10 | isSupported(): boolean; | |
|
|
11 | register(cb: (e: any) => void): void; | |
|
|
12 | } | |
|
|
13 | ||
|
|
14 | /** | |
|
|
15 | * Интерфейс поддерживающий асинхронную активацию | |
|
|
16 | */ | |
|
|
17 | export interface IActivatable { | |
|
|
18 | /** | |
|
|
19 | * @returns Boolean indicates the current state | |
|
|
20 | */ | |
|
|
21 | isActive(): boolean; | |
|
|
22 | ||
|
|
23 | /** | |
|
|
24 | * Starts the component activation | |
|
|
25 | * @param ct cancellation token for this operation | |
|
|
26 | */ | |
|
|
27 | activate(ct?: ICancellation) : Promise<void>; | |
|
|
28 | ||
|
|
29 | /** | |
|
|
30 | * Starts the component deactivation | |
|
|
31 | * @param ct cancellation token for this operation | |
|
|
32 | */ | |
|
|
33 | deactivate(ct?: ICancellation) : Promise<void>; | |
|
|
34 | ||
|
|
35 | /** | |
|
|
36 | * Sets the activation controller for this component | |
|
|
37 | * @param controller The activation controller | |
|
|
38 | * | |
|
|
39 | * Activation controller checks whether this component | |
|
|
40 | * can be activated and manages the active state of the | |
|
|
41 | * component | |
|
|
42 | */ | |
|
|
43 | setActivationController(controller: IActivationController); | |
|
|
44 | ||
|
|
45 | /** | |
|
|
46 | * Gets the current activation controller for this component | |
|
|
47 | */ | |
|
|
48 | getActivationController(): IActivationController; | |
|
|
49 | } | |
|
|
50 | ||
|
|
51 | export interface IActivationController { | |
|
|
52 | activating(component: IActivatable, ct?: ICancellation): Promise<void>; | |
|
|
53 | ||
|
|
54 | activated(component: IActivatable, ct?: ICancellation): Promise<void>; | |
|
|
55 | ||
|
|
56 | deactivating(component: IActivatable, ct?: ICancellation): Promise<void>; | |
|
|
57 | ||
|
|
58 | deactivated(component: IActivatable, ct?: ICancellation): Promise<void>; | |
|
|
59 | ||
|
|
60 | deactivate(ct?: ICancellation): Promise<void>; | |
|
|
61 | ||
|
|
62 | activate(component: IActivatable, ct?: ICancellation): Promise<void>; | |
|
|
63 | ||
|
|
64 | getActive(): IActivatable; | |
|
|
65 | } | |
|
|
66 | ||
|
|
67 | export interface IAsyncComponent { | |
|
|
68 | getCompletion(): Promise<void>; | |
|
|
69 | } | |
|
|
70 | ||
|
|
71 | export interface ICancellable { | |
|
|
72 | cancel(reason?: any): void; | |
|
|
73 | } | |
|
|
74 | ||
|
|
75 | export interface IObserver<T> { | |
|
|
76 | (x:T): void; | |
|
|
77 | } | |
|
|
78 | ||
|
|
79 | export interface IObservable<T> { | |
|
|
80 | on(observer: IObserver<T>): IDestroyable; | |
|
|
81 | ||
|
|
82 | wait(ct?: ICancellation) : Promise<T>; | |
|
|
83 | } No newline at end of file | |
| @@ -1,8 +1,6 | |||
|
|
1 |
import { IActivationController } from '. |
|
|
|
2 | import { IActivatable } from './IActivatable'; | |
|
|
1 | import { IActivationController, IActivatable, ICancellation } from '../interfaces'; | |
|
|
3 | 2 | import { AsyncComponent } from './AsyncComponent'; |
|
|
4 |
import { |
|
|
|
5 | import { EmptyCancellation } from '../EmptyCancellation'; | |
|
|
3 | import { Cancellation } from '../Cancellation'; | |
|
|
6 | 4 | import * as TraceSource from '../log/TraceSource'; |
|
|
7 | 5 | |
|
|
8 | 6 | type Constructor<T = {}> = new (...args: any[]) => T; |
| @@ -37,23 +35,21 function ActivatableMixin<TBase extends | |||
|
|
37 | 35 | await this._controller.activated(this, ct); |
|
|
38 | 36 | } |
|
|
39 | 37 | |
|
|
40 |
|
|
|
|
38 | activate(ct: ICancellation = Cancellation.none) { | |
|
|
39 | return this.runOperation(this._activateAsync.bind(this), ct); | |
|
|
40 | } | |
|
|
41 | ||
|
|
42 | async _activateAsync(ct: ICancellation) { | |
|
|
41 | 43 | if (this.isActive()) |
|
|
42 | 44 | return; |
|
|
43 | ct = this.startOperation(ct); | |
|
|
45 | ||
|
|
46 | await this.onActivating(ct); | |
|
|
47 | this._active = true; | |
|
|
44 | 48 | try { |
|
|
45 |
await this.onActivat |
|
|
|
46 | this._active = true; | |
|
|
47 | try { | |
|
|
48 | await this.onActivated(ct); | |
|
|
49 | } catch(e) { | |
|
|
50 | log.error("Suppressed onActivated error: {0}", e); | |
|
|
51 | } | |
|
|
52 | this.completeSuccess(); | |
|
|
49 | await this.onActivated(ct); | |
|
|
53 | 50 | } catch (e) { |
|
|
54 | this.completeFail(e); | |
|
|
51 | log.error("Suppressed onActivated error: {0}", e); | |
|
|
55 | 52 | } |
|
|
56 | return this.getCompletion(); | |
|
|
57 | 53 | } |
|
|
58 | 54 | |
|
|
59 | 55 | async onDeactivating(ct: ICancellation) { |
| @@ -66,25 +62,21 function ActivatableMixin<TBase extends | |||
|
|
66 | 62 | await this._controller.deactivated(this, ct); |
|
|
67 | 63 | } |
|
|
68 | 64 | |
|
|
69 |
|
|
|
|
65 | deactivate(ct: ICancellation = Cancellation.none) { | |
|
|
66 | return this.runOperation(this._deactivateAsync.bind(this), ct); | |
|
|
67 | } | |
|
|
68 | ||
|
|
69 | async _deactivateAsync(ct: ICancellation) { | |
|
|
70 | 70 | if (!this.isActive()) |
|
|
71 | 71 | return; |
|
|
72 |
|
|
|
|
72 | await this.onDeactivating(ct); | |
|
|
73 | this._active = false; | |
|
|
73 | 74 | try { |
|
|
74 |
await this.onDeactivat |
|
|
|
75 | this._active = false; | |
|
|
76 | try { | |
|
|
77 | await this.onDeactivated(ct); | |
|
|
78 | } catch(e) { | |
|
|
79 | log.error("Suppressed onDeactivated error: {0}", e); | |
|
|
80 | } | |
|
|
81 | this.completeSuccess(); | |
|
|
75 | await this.onDeactivated(ct); | |
|
|
82 | 76 | } catch (e) { |
|
|
83 | this.completeFail(e); | |
|
|
77 | log.error("Suppressed onDeactivated error: {0}", e); | |
|
|
84 | 78 | } |
|
|
85 | return this.getCompletion(); | |
|
|
86 | 79 | } |
|
|
87 | ||
|
|
88 | 80 | } |
|
|
89 | 81 | } |
|
|
90 | 82 | |
| @@ -1,47 +1,17 | |||
|
|
1 |
import { |
|
|
|
2 |
import { |
|
|
|
1 | import { Cancellation } from "../Cancellation"; | |
|
|
2 | import { IAsyncComponent, ICancellation } from "../interfaces"; | |
|
|
3 | 3 | |
|
|
4 | export class AsyncComponent { | |
|
|
5 | _completion: Promise<void>; | |
|
|
6 | ||
|
|
7 | _deferred: { | |
|
|
8 | resolve(): void | |
|
|
9 | reject(reason: any): void | |
|
|
10 | }; | |
|
|
4 | export class AsyncComponent implements IAsyncComponent { | |
|
|
5 | _completion: Promise<void> = Promise.resolve(); | |
|
|
11 | 6 | |
|
|
12 | 7 | getCompletion() { return this._completion }; |
|
|
13 | 8 | |
|
|
14 |
|
|
|
|
15 | if (this._deferred) | |
|
|
16 | throw new Error("The async operation is already pending"); | |
|
|
17 | ||
|
|
18 | this._completion = new Promise<void>((resolve, reject) => { | |
|
|
19 | this._deferred = { | |
|
|
20 | resolve: resolve, | |
|
|
21 | reject: reject | |
|
|
22 | } | |
|
|
23 | }); | |
|
|
24 | return ct; | |
|
|
25 | } | |
|
|
9 | runOperation(op: (ct: ICancellation) => any, ct: ICancellation = Cancellation.none) { | |
|
|
10 | // TODO create cancellation source here | |
|
|
11 | async function guard() { | |
|
|
12 | await op(ct); | |
|
|
13 | } | |
|
|
26 | 14 | |
|
|
27 | completeSuccess() { | |
|
|
28 | this._deferred.resolve(); | |
|
|
29 | this._deferred = null; | |
|
|
30 | } | |
|
|
31 | ||
|
|
32 | completeFail(reason: any) { | |
|
|
33 | this._deferred.reject(reason); | |
|
|
34 | this._deferred = null; | |
|
|
35 | } | |
|
|
36 | ||
|
|
37 | async runOperation(cb: (ct: ICancellation) => Promise<void>, ct: ICancellation = EmptyCancellation.default) { | |
|
|
38 | //safe.argumentNotNull(cb, "cb") | |
|
|
39 | ct = this.startOperation(ct); | |
|
|
40 | try { | |
|
|
41 | await cb(ct); | |
|
|
42 | this.completeSuccess(); | |
|
|
43 | } catch(e) { | |
|
|
44 | this.completeFail(e); | |
|
|
45 | } | |
|
|
15 | return this._completion = guard(); | |
|
|
46 | 16 | } |
|
|
47 | 17 | } No newline at end of file |
| @@ -78,15 +78,15 tape('controller activation', async func | |||
|
|
78 | 78 | t.comment("Active the component through the controller"); |
|
|
79 | 79 | await c.activate(a); |
|
|
80 | 80 | t.true(a.isActive(), "The component should successfully activate"); |
|
|
81 |
t. |
|
|
|
82 |
t. |
|
|
|
81 | t.equal(c.getActive(), a, "The controller should point to the activated component"); | |
|
|
82 | t.equal(a.getActivationController(), c, "The component should point to the controller"); | |
|
|
83 | 83 | |
|
|
84 | 84 | t.comment("Deactive the component throug the controller"); |
|
|
85 | 85 | await c.deactivate(); |
|
|
86 | 86 | |
|
|
87 | 87 | t.false(a.isActive(), "The component should successfully deactivate"); |
|
|
88 |
t. |
|
|
|
89 |
t. |
|
|
|
88 | t.equal(c.getActive(), null, "The controller shouldn't point to any component"); | |
|
|
89 | t.equal(a.getActivationController(), c, "The componet should point to it's controller"); | |
|
|
90 | 90 | |
|
|
91 | 91 | t.end(); |
|
|
92 | 92 | }); |
| @@ -3,7 +3,7 import * as tape from 'tape'; | |||
|
|
3 | 3 | |
|
|
4 | 4 | const sourceId = 'test/TraceSourceTests'; |
|
|
5 | 5 | |
|
|
6 | tape('', t => { | |
|
|
6 | tape('trace message', t => { | |
|
|
7 | 7 | let trace = TraceSource.get(sourceId); |
|
|
8 | 8 | |
|
|
9 | 9 | trace.level = TraceSource.DebugLevel; |
| @@ -11,7 +11,7 tape('', t => { | |||
|
|
11 | 11 | let h = trace.on((sender,level,msg) => { |
|
|
12 | 12 | t.equal(sender, trace, "sender should be the current trace source"); |
|
|
13 | 13 | t.equal(TraceSource.DebugLevel, level, "level should be debug level"); |
|
|
14 |
t.equal(msg, "Hello, World!", "The message should be formatted |
|
|
|
14 | t.equal(msg, "Hello, World!", "The message should be a formatted message"); | |
|
|
15 | 15 | |
|
|
16 | 16 | t.end(); |
|
|
17 | 17 | }); |
| @@ -19,4 +19,26 tape('', t => { | |||
|
|
19 | 19 | trace.debug("Hello, {0}!", "World"); |
|
|
20 | 20 | |
|
|
21 | 21 | h.destroy(); |
|
|
22 | }); | |
|
|
23 | ||
|
|
24 | tape('trace event', t => { | |
|
|
25 | let trace = TraceSource.get(sourceId); | |
|
|
26 | ||
|
|
27 | trace.level = TraceSource.DebugLevel; | |
|
|
28 | ||
|
|
29 | let event = { | |
|
|
30 | name: "custom event" | |
|
|
31 | }; | |
|
|
32 | ||
|
|
33 | let h = trace.on((sender,level,msg) => { | |
|
|
34 | t.equal(sender, trace, "sender should be the current trace source"); | |
|
|
35 | t.equal(TraceSource.DebugLevel, level, "level should be debug level"); | |
|
|
36 | t.equal(msg, event, "The message should be the specified object"); | |
|
|
37 | ||
|
|
38 | t.end(); | |
|
|
39 | }); | |
|
|
40 | ||
|
|
41 | trace.traceEvent(TraceSource.DebugLevel, event); | |
|
|
42 | ||
|
|
43 | h.destroy(); | |
|
|
22 | 44 | }); No newline at end of file |
|
|
1 | NO CONTENT: file was removed |
|
|
1 | NO CONTENT: file was removed |
|
|
1 | NO CONTENT: file was removed |
|
|
1 | NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now
