improved single/multiple guild subscription effect - use the { node } destructed vaglue from the effect parameter

This commit is contained in:
Michael Peters 2022-02-03 23:08:02 -06:00
parent 09390fad8f
commit 5b708f2a94
2 changed files with 58 additions and 38 deletions

View File

@ -5,14 +5,16 @@ const LOG = Logger.create(__filename, electronConsole);
import { ReactNode, useEffect } from "react";
import { atom, atomFamily, GetCallback, GetRecoilValue, RecoilState, RecoilValue, RecoilValueReadOnly, selector, selectorFamily, useSetRecoilState } from "recoil";
import { Changes, Channel, GuildMetadata, Member } from "../../data-types";
import { Changes, Channel, GuildMetadata, Member, Resource } from "../../data-types";
import CombinedGuild from "../../guild-combined";
import GuildsManager from "../../guilds-manager";
import { AutoVerifierChangesType } from '../../auto-verifier';
import { Conflictable, Connectable } from '../../guild-types';
import { IDQuery } from '../../auto-verifier-with-args';
// General typescript type that infers the arguments of a function
type Arguments<T> = T extends (...args: infer A) => unknown ? A : never;
// Ensures that a type is not undefined
type Defined<T> = T extends undefined ? never : T | Awaited<T>;
export type UnloadedValue = {
@ -101,6 +103,7 @@ export const allGuildsState = atom<CombinedGuild[] | null>({
});
interface RecoilLoadableAtomEffectParams<T> {
node: RecoilState<LoadableValue<T>>,
trigger: 'get' | 'set';
setSelf: (loadableValue: LoadableValue<T>) => void;
getPromise: <S>(recoilValue: RecoilValue<S>) => Promise<S>;
@ -109,15 +112,15 @@ interface RecoilLoadableAtomEffectParams<T> {
function createFetchValueFunc<T>(
getPromise: <S>(recoilValue: RecoilValue<S>) => Promise<S>,
guildId: number,
node: RecoilValue<LoadableValue<T>>,
setSelf: (loadableValue: LoadableValue<T>) => void,
stateAtomFamily: (guildId: number) => RecoilState<LoadableValue<T>>,
fetchFunc: (guild: CombinedGuild) => Promise<Defined<T>>,
): () => Promise<void> {
const fetchValueFunc = async () => {
const guild = await getPromise(guildState(guildId));
if (guild === null) return; // Can't send a request without an associated guild
const selfState = await getPromise(stateAtomFamily(guildId));
const selfState = await getPromise(node);
if (isPended(selfState)) return; // Don't send another request if we're already loading
setSelf(DEF_PENDED_VALUE);
@ -135,18 +138,20 @@ function createFetchValueFunc<T>(
// Creates an event handler that directly applies the result of the eventArgsMap as a loadedValue into self
function createDirectMappedEventHandler<T, XE extends keyof (Connectable | Conflictable)>(
getPromise: <S>(recoilValue: RecoilValue<S>) => Promise<S>,
guildId: number,
node: RecoilValue<LoadableValue<T>>,
setSelf: (loadableValue: LoadableValue<T>) => void,
stateAtomFamily: (guildId: number) => RecoilState<LoadableValue<T>>,
fetchValueFunc: () => Promise<void>,
eventArgsMap: (...args: Arguments<(Connectable & Conflictable)[XE]>) => Defined<T>
eventArgsMap: (...args: Arguments<(Connectable & Conflictable)[XE]>) => Defined<T>,
condition: (value: T) => boolean
): (Connectable & Conflictable)[XE] {
return ((...args: Arguments<(Connectable & Conflictable)[XE]>) => {
(async () => {
const selfState = await getPromise(stateAtomFamily(guildId));
const selfState = await getPromise(node);
if (isLoaded(selfState)) {
const value = eventArgsMap(...args);
setSelf(createLoadedValue(value, fetchValueFunc));
if (condition(value)) {
setSelf(createLoadedValue(value, fetchValueFunc));
}
}
})();
}) as (Connectable & Conflictable)[XE];
@ -166,9 +171,8 @@ function applyRemoved<T extends { id: string }>(value: T[], removedElements: T[]
// Useful for new-xxx, update-xxx, remove-xxx list events
function createListConnectableMappedEventHandler<T, XE extends keyof (Connectable | Conflictable)>(
getPromise: <S>(recoilValue: RecoilValue<S>) => Promise<S>,
guildId: number,
node: RecoilValue<LoadableValue<T[]>>,
setSelf: (loadableValue: LoadableValue<T[]>) => void,
stateAtomFamily: (guildId: number) => RecoilState<LoadableValue<T[]>>,
fetchValueFunc: () => Promise<void>,
eventArgsMap: (...args: Arguments<(Connectable & Conflictable)[XE]>) => Defined<T[]>,
sortFunc: (a: T, b: T) => number,
@ -178,7 +182,7 @@ function createListConnectableMappedEventHandler<T, XE extends keyof (Connectabl
// otherwise, I may have done this wrong. Forcing it to work with these calls
return ((...args: Arguments<(Connectable & Conflictable)[XE]>) => {
(async () => {
const selfState = await getPromise(stateAtomFamily(guildId));
const selfState = await getPromise(node);
if (isLoaded(selfState)) {
const eventArgsResult = eventArgsMap(...args);
const value = applyFunc(selfState.value, eventArgsResult, sortFunc);
@ -200,9 +204,8 @@ function applyChanges<T extends { id: string }>(value: T[], changes: Changes<T>,
// Useful for conflict-xxx list events
function createListConflictableMappedEventHandler<T, XE extends keyof (Connectable | Conflictable)>(
getPromise: <S>(recoilValue: RecoilValue<S>) => Promise<S>,
guildId: number,
node: RecoilValue<LoadableValue<T[]>>,
setSelf: (loadableValue: LoadableValue<T[]>) => void,
stateAtomFamily: (guildId: number) => RecoilState<LoadableValue<T[]>>,
fetchValueFunc: () => Promise<void>,
eventArgsMap: (...args: Arguments<(Connectable & Conflictable)[XE]>) => Changes<T>,
sortFunc: (a: T, b: T) => number,
@ -210,7 +213,7 @@ function createListConflictableMappedEventHandler<T, XE extends keyof (Connectab
): (Connectable & Conflictable)[XE] {
return ((...args: Arguments<(Connectable & Conflictable)[XE]>) => {
(async () => {
const selfState = await getPromise(stateAtomFamily(guildId));
const selfState = await getPromise(node);
if (isLoaded(selfState)) {
const eventArgsResult = eventArgsMap(...args);
const value = applyFunc(selfState.value, eventArgsResult, sortFunc);
@ -223,8 +226,10 @@ function createListConflictableMappedEventHandler<T, XE extends keyof (Connectab
interface SingleEventMappingParams<T, UE extends keyof Connectable, CE extends keyof Conflictable> {
updatedEventName: UE;
updatedEventArgsMap: (...args: Arguments<(Connectable & Conflictable)[UE]>) => Defined<T>;
updatedEventCondition?: (value: T) => boolean;
conflictEventName: CE;
conflictEventArgsMap: (...args: Arguments<(Connectable & Conflictable)[CE]>) => Defined<T>;
conflictEventCondition?: (value: T) => boolean;
}
function listenToSingle<
@ -234,8 +239,8 @@ function listenToSingle<
>(
getPromise: <S>(recoilValue: RecoilValue<S>) => Promise<S>,
guildId: number,
node: RecoilValue<LoadableValue<T>>,
setSelf: (loadableValue: LoadableValue<T>) => void,
stateAtomFamily: (guildId: number) => RecoilState<LoadableValue<T>>,
fetchValueFunc: () => Promise<void>,
eventMapping: SingleEventMappingParams<T, UE, CE>
) {
@ -251,8 +256,8 @@ function listenToSingle<
// I think the typed EventEmitter class isn't ready for this level of insane type safety
// otherwise, I may have done this wrong. Forcing it to work with these calls
onUpdateFunc = createDirectMappedEventHandler(getPromise, guildId, setSelf, stateAtomFamily, fetchValueFunc, eventMapping.updatedEventArgsMap);
onConflictFunc = createDirectMappedEventHandler(getPromise, guildId, setSelf, stateAtomFamily, fetchValueFunc, eventMapping.conflictEventArgsMap);
onUpdateFunc = createDirectMappedEventHandler(getPromise, node, setSelf, fetchValueFunc, eventMapping.updatedEventArgsMap, eventMapping.updatedEventCondition ?? (() => true));
onConflictFunc = createDirectMappedEventHandler(getPromise, node, setSelf, fetchValueFunc, eventMapping.conflictEventArgsMap, eventMapping.conflictEventCondition ?? (() => true));
guild.on(eventMapping.updatedEventName, onUpdateFunc);
guild.on(eventMapping.conflictEventName, onConflictFunc);
})();
@ -289,8 +294,8 @@ function listenToMultiple<
>(
getPromise: <S>(recoilValue: RecoilValue<S>) => Promise<S>,
guildId: number,
node: RecoilValue<LoadableValue<T[]>>,
setSelf: (loadableValue: LoadableValue<T[]>) => void,
stateAtomFamily: (guildId: number) => RecoilState<LoadableValue<T[]>>,
fetchValueFunc: () => Promise<void>,
sortFunc: (a: T, b: T) => number,
eventMapping: MultipleEventMappingParams<T, NE, UE, RE, CE>
@ -307,10 +312,10 @@ function listenToMultiple<
if (guild === null) return;
if (closed) return; // Make sure not to bind events if this closed while we were fetching the guild state
onNewFunc = createListConnectableMappedEventHandler(getPromise, guildId, setSelf, stateAtomFamily, fetchValueFunc, eventMapping.newEventArgsMap, sortFunc, applyNew);
onUpdateFunc = createListConnectableMappedEventHandler(getPromise, guildId, setSelf, stateAtomFamily, fetchValueFunc, eventMapping.updatedEventArgsMap, sortFunc, applyUpdated);
onRemoveFunc = createListConnectableMappedEventHandler(getPromise, guildId, setSelf, stateAtomFamily, fetchValueFunc, eventMapping.updatedEventArgsMap, sortFunc, applyRemoved);
onConflictFunc = createListConflictableMappedEventHandler(getPromise, guildId, setSelf, stateAtomFamily, fetchValueFunc, eventMapping.conflictEventArgsMap, sortFunc, applyChanges);
onNewFunc = createListConnectableMappedEventHandler(getPromise, node, setSelf, fetchValueFunc, eventMapping.newEventArgsMap, sortFunc, applyNew);
onUpdateFunc = createListConnectableMappedEventHandler(getPromise, node, setSelf, fetchValueFunc, eventMapping.updatedEventArgsMap, sortFunc, applyUpdated);
onRemoveFunc = createListConnectableMappedEventHandler(getPromise, node, setSelf, fetchValueFunc, eventMapping.updatedEventArgsMap, sortFunc, applyRemoved);
onConflictFunc = createListConflictableMappedEventHandler(getPromise, node, setSelf, fetchValueFunc, eventMapping.conflictEventArgsMap, sortFunc, applyChanges);
guild.on(eventMapping.updatedEventName, onUpdateFunc);
guild.on(eventMapping.conflictEventName, onConflictFunc);
})();
@ -330,13 +335,12 @@ function singleGuildSubscriptionEffect<
CE extends keyof Conflictable, // Conflict Event
>(
guildId: number,
stateAtomFamily: (guildId: number) => RecoilState<LoadableValue<T>>,
fetchFunc: (guild: CombinedGuild) => Promise<Defined<T>>,
eventMapping: SingleEventMappingParams<T, UE, CE>
) {
return (params: RecoilLoadableAtomEffectParams<T>) => {
const { trigger, setSelf, getPromise } = params;
const fetchValueFunc = createFetchValueFunc(getPromise, guildId, setSelf, stateAtomFamily, fetchFunc);
const { node, trigger, setSelf, getPromise } = params;
const fetchValueFunc = createFetchValueFunc(getPromise, guildId, node, setSelf, fetchFunc);
// Fetch initial value on first get
if (trigger === 'get') {
@ -344,7 +348,7 @@ function singleGuildSubscriptionEffect<
}
// Listen to changes
const cleanup = listenToSingle(getPromise, guildId, setSelf, stateAtomFamily, fetchValueFunc, eventMapping);
const cleanup = listenToSingle(getPromise, guildId, node, setSelf, fetchValueFunc, eventMapping);
return () => {
cleanup();
@ -360,14 +364,13 @@ function multipleGuildSubscriptionEffect<
CE extends keyof Conflictable
>(
guildId: number,
stateAtomFamily: (guildId: number) => RecoilState<LoadableValue<T[]>>,
fetchFunc: (guild: CombinedGuild) => Promise<Defined<T[]>>,
sortFunc: (a: T, b: T) => number,
eventMapping: MultipleEventMappingParams<T, NE, UE, RE, CE>,
) {
return (params: RecoilLoadableAtomEffectParams<T[]>) => {
const { trigger, setSelf, getPromise } = params;
const fetchValueFunc = createFetchValueFunc(getPromise, guildId, setSelf, stateAtomFamily, fetchFunc);
const { node, trigger, setSelf, getPromise } = params;
const fetchValueFunc = createFetchValueFunc(getPromise, guildId, node, setSelf, fetchFunc);
// Fetch initial value on first get
if (trigger === 'get') {
@ -375,7 +378,7 @@ function multipleGuildSubscriptionEffect<
}
// Listen to changes
const cleanup = listenToMultiple(getPromise, guildId, setSelf, stateAtomFamily, fetchValueFunc, sortFunc, eventMapping);
const cleanup = listenToMultiple(getPromise, guildId, node, setSelf, fetchValueFunc, sortFunc, eventMapping);
return () => {
cleanup();
@ -384,13 +387,12 @@ function multipleGuildSubscriptionEffect<
}
// You probably want currGuildMetaState
export const guildMetaState: (guildId: number) => RecoilState<LoadableValue<GuildMetadata>> = atomFamily<LoadableValue<GuildMetadata>, number>({
export const guildMetaState = atomFamily<LoadableValue<GuildMetadata>, number>({
key: 'guildMetaState',
default: DEF_UNLOADED_VALUE,
effects_UNSTABLE: (guildId: number) => [
singleGuildSubscriptionEffect(
guildId,
guildMetaState,
async (guild: CombinedGuild) => await guild.fetchMetadata(),
{
updatedEventName: 'update-metadata',
@ -403,13 +405,32 @@ export const guildMetaState: (guildId: number) => RecoilState<LoadableValue<Guil
dangerouslyAllowMutability: true
});
const guildMembersState: (guildId: number) => RecoilState<LoadableValue<Member[]>> = atomFamily<LoadableValue<Member[]>, number>({
export const guildResourceState = atomFamily<LoadableValue<Resource>, { guildId: number, resourceId: string }>({
key: 'guildResourceState',
default: DEF_UNLOADED_VALUE,
effects_UNSTABLE: (param: { guildId: number, resourceId: string }) => [
singleGuildSubscriptionEffect(
param.guildId,
async (guild: CombinedGuild) => await guild.fetchResource(param.resourceId),
{
updatedEventName: 'update-resource',
updatedEventArgsMap: (updatedResource: Resource) => updatedResource,
updatedEventCondition: (resource: Resource) => resource.id === param.resourceId,
conflictEventName: 'conflict-resource',
conflictEventArgsMap: (_query: IDQuery, _changeType: AutoVerifierChangesType, _oldResource: Resource, newResource: Resource) => newResource,
conflictEventCondition: (resource: Resource) => resource.id === param.resourceId
}
)
],
dangerouslyAllowMutability: true
});
const guildMembersState = atomFamily<LoadableValue<Member[]>, number>({
key: 'guildMembersState',
default: DEF_UNLOADED_VALUE,
effects_UNSTABLE: (guildId: number) => [
multipleGuildSubscriptionEffect(
guildId,
guildMembersState,
async (guild: CombinedGuild) => await guild.fetchMembers(),
Member.sortForList,
{
@ -451,13 +472,12 @@ export const guildSelfMemberState = selectorFamily<LoadableValue<Member>, number
dangerouslyAllowMutability: true
});
const guildChannelsState: (guildId: number) => RecoilState<LoadableValue<Channel[]>> = atomFamily<LoadableValue<Channel[]>, number>({
const guildChannelsState = atomFamily<LoadableValue<Channel[]>, number>({
key: 'guildChannelsState',
default: DEF_UNLOADED_VALUE,
effects_UNSTABLE: (guildId: number) => [
multipleGuildSubscriptionEffect(
guildId,
guildChannelsState,
async (guild: CombinedGuild) => await guild.fetchChannels(),
Channel.sortByIndex,
{

View File

@ -788,7 +788,7 @@ export function useSoftImageSrcResourceSubscription(guild: CombinedGuild, resour
* fetchError: Any error from fetching
* ]
*/
export function useChannelsSubscription(guild: CombinedGuild) {
function useChannelsSubscription(guild: CombinedGuild) {
const fetchChannelsFunc = useCallback(async () => {
return await guild.fetchChannels();
}, [ guild ]);
@ -838,7 +838,7 @@ function useMembersSubscription(guild: CombinedGuild) {
* TODO: __fetchError: Any error from fetching
* ]
*/
export function useSelfMemberSubscription(guild: CombinedGuild): [
function useSelfMemberSubscription(guild: CombinedGuild): [
selfMemberResult: SubscriptionResult<Member | null> | null
] {
const [ _fetchRetryCallable, membersResult, _fetchError ] = useMembersSubscription(guild);