Skip to content

Commit

Permalink
Debounce helper methodCreators.
Browse files Browse the repository at this point in the history
  • Loading branch information
REllEK-IO committed Oct 19, 2023
1 parent f6272cb commit 338a1e4
Show file tree
Hide file tree
Showing 13 changed files with 538 additions and 117 deletions.
3 changes: 0 additions & 3 deletions src/concepts/counter/qualities/add.quality.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { map, Subject } from 'rxjs';
import { Action, ActionType, prepareActionCreator } from '../../../model/action';
import { defaultMethodCreator, Method, MethodCreator } from '../../../model/concept';
import { strategySuccess } from '../../../model/actionStrategy';
import { Counter } from '../counter.concept';
import { createQuality } from '../../../model/concept';
import { counterSelectCount } from '../counter.selector';
import { axiumConclude } from '../../axium/qualities/conclude.quality';

export const counterAddType: ActionType = 'Counter Add';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { MethodCreator, defaultReducer } from '../../../model/concept';
import { prepareActionCreator } from '../../../model/action';
import { createQuality } from '../../../model/concept';
import { createAsyncMethodDebounce } from '../../../model/method';
import { strategySuccess } from '../../../model/actionStrategy';

export const experimentAsyncDebounceNextActionNodeType = 'Experiment will debounce incoming actions within set duration asynchronously';
export const experimentAsyncDebounceNextActionNode = prepareActionCreator(experimentAsyncDebounceNextActionNodeType);

export const experimentDebounceNextActionNodeMethodCreator: MethodCreator = () => createAsyncMethodDebounce((controller, action) => {
setTimeout(() => {
if (action.strategy) {
controller.fire(strategySuccess(action.strategy));
} else {
controller.fire(action);
}
}, 50);
}, 500);

export const asyncDebounceNextActionNodeQuality = createQuality(
experimentAsyncDebounceNextActionNodeType,
defaultReducer,
experimentDebounceNextActionNodeMethodCreator
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { MethodCreator, defaultReducer } from '../../../model/concept';
import { prepareActionCreator } from '../../../model/action';
import { createQuality } from '../../../model/concept';
import { createMethodDebounce } from '../../../model/method';
import { strategySuccess } from '../../../model/actionStrategy';

export const experimentDebounceNextActionNodeType = 'Experiment will debounce incoming actions within set duration';
export const experimentDebounceNextActionNode = prepareActionCreator(experimentDebounceNextActionNodeType);

export const experimentDebounceNextActionNodeMethodCreator: MethodCreator = () => createMethodDebounce((action) => {
if (action.strategy) {
return strategySuccess(action.strategy);
} else {
return action;
}
}, 500);

export const debounceNextActionNodeQuality = createQuality(
experimentDebounceNextActionNodeType,
defaultReducer,
experimentDebounceNextActionNodeMethodCreator
);
33 changes: 33 additions & 0 deletions src/concepts/experiment/strategies/asyncDebounceAddOne.strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { createStrategy, ActionStrategy, ActionStrategyParameters, createActionNode } from '../../../model/actionStrategy';
import { counterAdd } from '../../counter/qualities/add.quality';
import { experimentAsyncDebounceNextActionNode } from '../qualities/debounceAsyncNextActionNode.quality';
import { experimentDebounceNextActionNode } from '../qualities/debounceNextActionNode.quality';

export const experimentAsyncDebounceAddOneTopic = 'Async debounce add one';
export function experimentAsyncDebounceAddOneStrategy(): ActionStrategy {
const stepTwo = createActionNode(counterAdd(), {
successNode: null,
successNotes: {
preposition: '',
denoter: 'One;',
},
failureNode: null,
agreement: 1000,
});
const stepOne = createActionNode(experimentAsyncDebounceNextActionNode(), {
successNode: stepTwo,
successNotes: {
preposition: '',
denoter: 'One;',
},
failureNode: null,
agreement: 1000,
});

const params: ActionStrategyParameters = {
topic: experimentAsyncDebounceAddOneTopic,
initialNode: stepOne,
};

return createStrategy(params);
}
32 changes: 32 additions & 0 deletions src/concepts/experiment/strategies/debounceAddOne.strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { createStrategy, ActionStrategy, ActionStrategyParameters, createActionNode } from '../../../model/actionStrategy';
import { counterAdd } from '../../counter/qualities/add.quality';
import { experimentDebounceNextActionNode } from '../qualities/debounceNextActionNode.quality';

export const experimentDebounceAddOneTopic = 'Debounce add one';
export function experimentDebounceAddOneStrategy(): ActionStrategy {
const stepTwo = createActionNode(counterAdd(), {
successNode: null,
successNotes: {
preposition: '',
denoter: 'One;',
},
failureNode: null,
agreement: 1000,
});
const stepOne = createActionNode(experimentDebounceNextActionNode(), {
successNode: stepTwo,
successNotes: {
preposition: '',
denoter: 'One;',
},
failureNode: null,
agreement: 1000,
});

const params: ActionStrategyParameters = {
topic: experimentDebounceAddOneTopic,
initialNode: stepOne,
};

return createStrategy(params);
}
9 changes: 8 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,15 @@ export {
createMethod,
createAsyncMethod,
createMethodWithConcepts,
createAsyncMethodWithConcepts
createAsyncMethodWithConcepts,
createMethodDebounce,
createAsyncMethodDebounce,
createMethodDebounceWithConcepts,
createAsyncMethodDebounceWithConcepts
} from './model/method';
export {
debounceAction
} from './model/debounceAction';
export type { Action, ActionType } from './model/action';
export { primeAction, createAction, getSemaphore, prepareActionCreator, prepareActionWithPayloadCreator } from './model/action';
export { createConcept, createQuality, defaultReducer, defaultMethodCreator } from './model/concept';
Expand Down
17 changes: 14 additions & 3 deletions src/model/actionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,34 @@ export class ActionController extends Subject<Action> {
this.timer.unref();
}
let nextAction;
let end = true;
// Logically Determined axiumConclude
if (action.semaphore[3] === 3) {
end = false;
}
if (action.strategy) {
nextAction = action;
} else if (action.semaphore[3] !== 1) {
// Logically Determined axiumConclude
} else if (action.semaphore[3] === 3) {
nextAction = action;
// Logically Determined axiumBadAction
} else if (!action.strategy && action.semaphore[3] !== 1) {
const conclude = axiumConclude();
nextAction = {
...action,
...conclude
};
} else {
} else {
nextAction = action;
}
const { observers } = this;
const len = observers.length;
for (let i = 0; i < len; i++) {
observers[i].next(nextAction);
}
this.complete();
if (end) {
this.complete();
}
}
}
}
Expand Down
165 changes: 165 additions & 0 deletions src/model/debounceAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import {
MonoTypeOperatorFunction,
Observable,
OperatorFunction,
SchedulerAction,
SchedulerLike,
Subscriber,
Subscription,
asyncScheduler
} from 'rxjs';
import { Action } from './action';
import { axiumConclude } from '../concepts/axium/qualities/conclude.quality';

function hasLift(source: any): source is { lift: InstanceType<typeof Observable>['lift'] } {
return typeof source?.lift === 'function';
}

function operate<T, R>(
init: (liftedSource: Observable<T>, subscriber: Subscriber<R>) => (() => void) | void
): OperatorFunction<T, R> {
return (source: Observable<T>) => {
if (hasLift(source)) {
// eslint-disable-next-line consistent-return
return source.lift(function (this: Subscriber<R>, liftedSource: Observable<T>) {
try {
return init(liftedSource, this);
} catch (err) {
this.error(err);
}
});
}
throw new TypeError('Unable to lift unknown Observable type');
};
}

class OperatorSubscriber<T> extends Subscriber<T> {
constructor(
destination: Subscriber<any>,
onNext?: (value: T) => void,
onComplete?: () => void,
onError?: (err: any) => void,
private onFinalize?: () => void,
private shouldUnsubscribe?: () => boolean
) {
super(destination);
this._next = onNext
? function (this: OperatorSubscriber<T>, value: T) {
try {
onNext(value);
} catch (err) {
destination.error(err);
}
}
: super._next;
this._error = onError
? function (this: OperatorSubscriber<T>, err: any) {
try {
onError(err);
} catch (error) {
// Send any errors that occur down stream.
destination.error(error);
} finally {
// Ensure finalization.
this.unsubscribe();
}
}
: super._error;
this._complete = onComplete
? function (this: OperatorSubscriber<T>) {
try {
onComplete();
} catch (err) {
// Send any errors that occur down stream.
destination.error(err);
} finally {
// Ensure finalization.
this.unsubscribe();
}
}
: super._complete;
}

unsubscribe() {
if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) {
const { closed } = this;
super.unsubscribe();
!closed && this.onFinalize?.();
}
}
}

function createOperatorSubscriber<T>(
destination: Subscriber<any>,
onNext?: (value: T) => void,
onComplete?: () => void,
onError?: (err: any) => void,
onFinalize?: () => void
): Subscriber<T> {
return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize);
}

/**
* This will prevent all actions for the specified duration, but will still emit actions as axiumConclude
* Thus this needs to be taken into account in the Method using debounceAction if implemented directly.
* But will be handled automatically in actionControllers and associated debounce createMethods.
*/
export function debounceAction(dueTime: number, scheduler: SchedulerLike = asyncScheduler): MonoTypeOperatorFunction<Action> {
return operate((source, subscriber) => {
let activeTask: Subscription | null = null;
let lastValue: Action | null = null;
let lastTime: number | null = null;

const emit = () => {
if (activeTask) {
activeTask.unsubscribe();
activeTask = null;
const value = lastValue!;
lastValue = null;
subscriber.next(value);
}
};
function emitWhenIdle(this: SchedulerAction<unknown>) {
const targetTime = lastTime! + dueTime;
const now = scheduler.now();
if (now < targetTime) {
activeTask = this.schedule(undefined, targetTime - now);
subscriber.add(activeTask);
return;
}

emit();
}

source.subscribe(
createOperatorSubscriber(
subscriber,
(value: Action) => {
lastValue = value;
lastTime = scheduler.now();
if (!activeTask) {
activeTask = scheduler.schedule(emitWhenIdle, dueTime);
subscriber.add(activeTask);
} else {
// All this code just to place this code block.
const conclude = axiumConclude();
subscriber.next(
{
...value,
...conclude,
}
);
}
},
() => {
emit();
subscriber.complete();
},
undefined,
() => {
lastValue = activeTask = null;
}
)
);
});
}
Loading

0 comments on commit 338a1e4

Please sign in to comment.