This commit is contained in:
Morten Olsen
2022-05-06 14:39:49 +02:00
parent d83a4aebc7
commit 7057e11e96
18 changed files with 257 additions and 51 deletions

View File

@@ -1,7 +1,7 @@
import { Context, GraphNode } from "#/types/graph";
import { UserLocation } from "#/types/location";
import { Task } from "#/types/task";
import { getNext } from "./get-next";
import { getImpossible, getNext } from "./get-next";
enum Strategies {
all = 'all',
@@ -13,6 +13,7 @@ type RunningStatus = {
current: 'running';
nodes: number;
start: Date;
strategy: Strategies,
cancel: () => void;
}
@@ -21,6 +22,7 @@ type CompletedStatus = {
start: Date;
end: Date;
nodes: number;
strategy: Strategies,
}
type Status = RunningStatus | CompletedStatus;
@@ -65,38 +67,62 @@ const buildGraph = async ({
sleepTime = 10,
}: BuildGraphOptions) => {
const start = new Date();
let leafs: GraphNode[] = [{
let nodeCount = 0;
let running = true;
const { remaining, impossible } = getImpossible(tasks, time);
let leafList: GraphNode[] = [{
location,
time: {
end: time,
start: time,
},
score: 0,
remainingTasks: tasks,
impossibeTasks: [],
remainingTasks: remaining,
impossibeTasks: impossible,
status: {
dead: false,
completed: false,
},
}];
let nodes = 0;
let running = true;
const final: GraphNode[] = [];
const completedList: GraphNode[] = [];
const deadList: GraphNode[] = [];
const complete = (nodes: GraphNode[]) => {
if (callback) {
callback({
current: 'completed',
nodes: nodeCount,
start,
end: new Date(),
strategy,
});
}
return nodes.sort((a, b) => b.score - a.score);
}
while (true) {
nodes++;
nodeCount++;
if (!running) {
return [];
}
const node = leafs.pop();
if (
leafList.length === 0
&& completedList.length === 0
&& strategy !== Strategies.all
) {
strategy = Strategies.all;
leafList.push(...deadList);
}
const node = leafList.pop();
if (!node) {
break;
}
if (nodes % batchSize === 1) {
if (nodeCount % batchSize === 0) {
if (callback) {
callback({
current: 'running',
nodes,
nodes: nodeCount,
strategy,
start,
cancel: () => {
running = false;
@@ -106,38 +132,28 @@ const buildGraph = async ({
await sleep(sleepTime);
}
const next = await getNext(node, context);
const [alive, completed] = fil([
n => !n.status.dead && !n.status.completed,
n => !!n.status.completed && !n.status.dead
const [alive, completed, dead] = fil([
n => (strategy === Strategies.all || !n.status.dead) && !n.status.completed,
n => !!n.status.completed && (strategy === Strategies.all || !n.status.dead),
n => n.status.dead,
], next);
leafs.push(...alive);
leafList.push(...alive);
if (strategy === Strategies.firstValid && completed.length > 0) {
if (callback) {
callback({ current: 'completed', nodes, start, end: new Date() })
}
return completed;
return complete(completed);
}
if (completed.length > 0) {
final.push(...completed)
completedList.push(...completed)
}
if (strategy === Strategies.firstComplet) {
const fullComplete = completed.find(c => c.impossibeTasks.length === 0);
if (fullComplete) {
if (callback) {
callback({ current: 'completed', nodes, start, end: new Date() })
}
return [fullComplete];
return complete([fullComplete]);
}
}
deadList.push(...dead);
}
console.log('nodes', nodes);
if (callback) {
callback({ current: 'completed', nodes, start, end: new Date() })
}
return final
.filter(n => n.status.completed)
.sort((a, b) => b.score - a.score);
return complete(completedList);
}
export type { Status, BuildGraphOptions };

View File

@@ -13,7 +13,7 @@ type GetImpossibleResult = {
impossible: Task[];
}
const getImpossible = (
export const getImpossible = (
tasks: Task[],
time: Date,
) => {
@@ -50,15 +50,15 @@ const calculateScore = ({
score += task.priority * 10;
impossible.forEach((task) => {
if (task.required) {
score -= 1000;
score -= 10000 + (1 * task.priority);
} else {
score -= task.priority;
score -= 100 + (1 * task.priority);
}
});
});
if (transition) {
const minutes = transition.time / 1000 / 60
score -= minutes;
score -= 10 + (1 * minutes);
}
return score;
}

View File

@@ -0,0 +1,15 @@
import { createContext } from 'react';
import { Strategies } from "./algorithm/build-graph";
type PlannerOptions = {
strategy: Strategies;
}
type PlannerContextValue = {
options: PlannerOptions;
setOptions: (options: Partial<PlannerOptions>) => void;
}
const PlannerContext = createContext<PlannerContextValue>(undefined as any);
export type { PlannerContextValue, PlannerOptions };
export { PlannerContext };

View File

@@ -5,9 +5,10 @@ import { useAsyncCallback } from "#/hooks/async";
import { UserLocation } from "#/types/location";
import { useDate } from "../calendar";
import { useTasksWithContext } from "../agenda-context";
import { useMemo, useState } from "react";
import { useContext, useMemo, useState } from "react";
import { PlanItem } from "#/types/plans";
import { Task } from "#/types/task";
import { PlannerContext } from "./context";
export type UsePlanOptions = {
location: UserLocation;
@@ -23,10 +24,21 @@ export type UsePlan = [
}
]
export const usePlanOptions = () => {
const { options } = useContext(PlannerContext);
return options;
}
export const useSetPlanOptions = () => {
const { setOptions } = useContext(PlannerContext);
return setOptions;
}
export const usePlan = ({
location,
}: UsePlanOptions): UsePlan => {
const today = useDate();
const planOptions = usePlanOptions();
const [status, setStatus] = useState<Status>();
const all = useTasksWithContext();
const enabled = useMemo(() => all.filter(f => f.enabled), [all])
@@ -37,7 +49,7 @@ export const usePlan = ({
location,
time: start || today,
tasks: enabled,
strategy: Strategies.firstComplet,
strategy: planOptions.strategy,
context: {
getTransition,
},
@@ -50,7 +62,7 @@ export const usePlan = ({
agenda: day,
};
},
[today, location, all, setStatus],
[today, location, all, setStatus, planOptions],
);
return [

View File

@@ -1 +1,4 @@
export { PlannerProvider } from './provider';
export type { PlannerOptions } from './context';
export { Strategies } from './algorithm/build-graph';
export * from './hooks';

View File

@@ -0,0 +1,32 @@
import { ReactNode, useCallback, useState } from 'react';
import { Strategies } from './algorithm/build-graph';
import { PlannerContext, PlannerOptions } from './context';
type PlannerProviderProps = {
children: ReactNode;
};
const PlannerProvider: React.FC<PlannerProviderProps> = ({ children }) => {
const [options, setOwnOptions] = useState<PlannerOptions>({
strategy: Strategies.firstComplet,
})
const setOptions = useCallback(
(next: Partial<PlannerOptions>) => {
setOwnOptions(current => ({
...current,
...next,
}))
},
[setOwnOptions],
);
return (
<PlannerContext.Provider value={{ options, setOptions }}>
{children}
</PlannerContext.Provider>
);
}
export type { PlannerProviderProps };
export { PlannerProvider };

View File

@@ -3,6 +3,7 @@ import { ReactNode } from "react"
import { AgendaContextProvider } from "./agenda-context"
import { CalendarProvider } from "./calendar"
import { LocationProvider } from "./location"
import { PlannerProvider } from "./planner"
import { RoutinesProvider } from "./routines"
type SetupProps = {
@@ -21,7 +22,9 @@ const Setup: React.FC<SetupProps> = ({
<RoutinesProvider>
<LocationProvider getTransition={getTransit} lookup={() => []}>
<AgendaContextProvider day={day}>
{children}
<PlannerProvider>
{children}
</PlannerProvider>
</AgendaContextProvider>
</LocationProvider>
</RoutinesProvider>