##// END OF EJS Templates
refactoring, all common interfaces placed to core/interfaces.ts...
cin -
r13:745612edbd74 propose cancellat...
parent child
Show More
@@ -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,95 +1,87
1 import { IActivationController } from './IActivationController';
2 import { IActivatable } from './IActivatable';
1 import { IActivationController, IActivatable, ICancellation } from '../interfaces';
3 2 import { AsyncComponent } from './AsyncComponent';
4 import { ICancellation } from '../ICancellation';
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;
9 7
10 8 const log = TraceSource.get('@implab/core/components/ActivatableMixin');
11 9
12 10 function ActivatableMixin<TBase extends Constructor<AsyncComponent>>(Base: TBase) {
13 11 return class extends Base implements IActivatable {
14 12 _controller: IActivationController;
15 13
16 14 _active: boolean;
17 15
18 16 isActive() {
19 17 return this._active;
20 18 }
21 19
22 20 getActivationController() {
23 21 return this._controller;
24 22 }
25 23
26 24 setActivationController(controller: IActivationController) {
27 25 this._controller = controller;
28 26 }
29 27
30 28 async onActivating(ct: ICancellation) {
31 29 if (this._controller)
32 30 await this._controller.activating(this, ct);
33 31 }
34 32
35 33 async onActivated(ct: ICancellation) {
36 34 if (this._controller)
37 35 await this._controller.activated(this, ct);
38 36 }
39 37
40 async activate(ct: ICancellation = EmptyCancellation.default) {
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.onActivating(ct);
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) {
60 56 if (this._controller)
61 57 await this._controller.deactivating(this, ct);
62 58 }
63 59
64 60 async onDeactivated(ct: ICancellation) {
65 61 if (this._controller)
66 62 await this._controller.deactivated(this, ct);
67 63 }
68 64
69 async deactivate(ct: ICancellation = EmptyCancellation.default) {
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 ct = this.startOperation(ct);
72 await this.onDeactivating(ct);
73 this._active = false;
73 74 try {
74 await this.onDeactivating(ct);
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
91 83 namespace ActivatableMixin {
92 84 export const traceSource = log;
93 85 }
94 86
95 87 export = ActivatableMixin; No newline at end of file
@@ -1,47 +1,17
1 import { ICancellation } from "../ICancellation";
2 import { EmptyCancellation } from "../EmptyCancellation";
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 startOperation(ct: ICancellation = EmptyCancellation.default) {
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
@@ -1,110 +1,110
1 1 import * as tape from 'tape';
2 2 import * as ActivatableMixin from '@implab/core/components/ActivatableMixin';
3 3 import { AsyncComponent } from '@implab/core/components/AsyncComponent';
4 4 import { IActivationController } from '@implab/core/components/IActivationController';
5 5 import { IActivatable } from '@implab/core/components/IActivatable';
6 6 import { ICancellation } from '@implab/core/ICancellation';
7 7 import { EmptyCancellation } from '@implab/core/EmptyCancellation';
8 8
9 9 class SimpleActivatable extends ActivatableMixin(AsyncComponent) {
10 10
11 11 }
12 12
13 13 class MockActivationController implements IActivationController {
14 14
15 15 _active: IActivatable = null;
16 16
17 17
18 18 getActive() : IActivatable {
19 19 return this._active;
20 20 }
21 21
22 22 async deactivate() {
23 23 if (this._active)
24 24 await this._active.deactivate();
25 25 this._active = null;
26 26 }
27 27
28 28 async activate(component: IActivatable) {
29 29 if (!component || component.isActive())
30 30 return;
31 31 component.setActivationController(this);
32 32
33 33 await component.activate();
34 34 }
35 35
36 36 async activating(component: IActivatable, ct: ICancellation = EmptyCancellation.default) {
37 37 if (component != this._active)
38 38 await this.deactivate();
39 39 }
40 40
41 41 async activated(component: IActivatable, ct: ICancellation = EmptyCancellation.default) {
42 42 this._active = component;
43 43 }
44 44
45 45 async deactivating(component: IActivatable, ct: ICancellation = EmptyCancellation.default) {
46 46
47 47 }
48 48
49 49 async deactivated(component: IActivatable, ct: ICancellation = EmptyCancellation.default) {
50 50 if (this._active == component)
51 51 this._active = null;
52 52 }
53 53 }
54 54
55 55 tape('simple activation',async function(t){
56 56
57 57 let a = new SimpleActivatable();
58 58 t.false(a.isActive());
59 59
60 60 await a.activate();
61 61 t.true(a.isActive());
62 62
63 63 await a.deactivate();
64 64 t.false(a.isActive());
65 65
66 66 t.end();
67 67 });
68 68
69 69 tape('controller activation', async function(t) {
70 70
71 71 let a = new SimpleActivatable();
72 72 let c = new MockActivationController();
73 73
74 74 t.false(a.isActive(), "the component is not active by default");
75 75 t.assert(c.getActive() == null, "the activation controller doesn't have an active component by default");
76 76 t.assert(a.getActivationController() == null, "the component doesn't have an activation controller by default");
77 77
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.assert(c.getActive() == a, "The controller should point to the activated component");
82 t.assert(a.getActivationController() == c, "The component should point to the controller");
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.assert(c.getActive() == null, "The controller shouldn't point to any component");
89 t.assert(a.getActivationController() == c, "The componet should point to it's controller");
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 });
93 93
94 94 tape('handle error in onActivating', async function(t) {
95 95 let a = new SimpleActivatable();
96 96
97 97 a.onActivating = async function() {
98 98 throw "Should fail";
99 99 };
100 100
101 101 try {
102 102 await a.activate();
103 103 t.fail("activation should fail");
104 104 } catch {
105 105 }
106 106
107 107 t.false(a.isActive(), "the component should remain inactive");
108 108
109 109 t.end();
110 110 }); No newline at end of file
@@ -1,22 +1,44
1 1 import * as TraceSource from '@implab/core/log/TraceSource'
2 2 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;
10 10
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 correctly");
14 t.equal(msg, "Hello, World!", "The message should be a formatted message");
15 15
16 16 t.end();
17 17 });
18 18
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