mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 15:36:18 +00:00
Pilot: extract useInventoryFurni reducers to a pure module
The four useMessageEvent handlers in useInventoryFurniState (furniture list add/update, list, removed, plus the dead post-it-placed listener) were inlined as ~250 LOC of merge logic inside setGroupItems callbacks. Three things change: - The three meaningful reducers move to useInventoryFurni.reducers.ts as applyFurnitureListAddOrUpdate / applyFurnitureList / applyFurnitureListRemoved, plus two helpers clearUnseenFlags and refreshGroupItemsLocalization for the existing effect-driven mutations. Side effects (CreateLinkEvent, attemptItemPlacement, dispatchAdded) are passed in via a ctx object so the reducers stay easy to test. - The module-level furniMsgFragments buffer becomes a useRef, removing a latent bug where two simultaneous client instances would have trampled each other's fragments. - The empty FurniturePostItPlacedEvent handler is dropped (dead code). useInventoryFurni still owns groupItems via useState so the existing effect-driven setters (unseen flag reset, localization refresh) keep working; the message handlers now call setGroupItems(prev => applyX(prev, event, ctx)) with the extracted reducers.
This commit is contained in:
@@ -0,0 +1,227 @@
|
|||||||
|
import { CreateLinkEvent, FurnitureListAddOrUpdateEvent, FurnitureListEvent, FurnitureListItemParser, FurnitureListRemovedEvent } from '@nitrots/nitro-renderer';
|
||||||
|
import { CloneObject, FurnitureItem, GroupItem, UnseenItemCategory, addFurnitureItem, attemptItemPlacement, cancelRoomObjectPlacement, getAllItemIds, getPlacingItemId, mergeFurniFragments } from '../../api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pure reducers for furniture inventory state. Each takes the current
|
||||||
|
* GroupItem[] state plus the inbound event plus a context object carrying
|
||||||
|
* the cross-cutting helpers (unseen tracker, ui-event dispatcher).
|
||||||
|
*
|
||||||
|
* Side effects (CreateLinkEvent, attemptItemPlacement, dispatchAdded,
|
||||||
|
* cancelRoomObjectPlacement) are intentionally kept here to preserve the
|
||||||
|
* exact behavior of the original useInventoryFurni — they fire when the
|
||||||
|
* state transition demands them. The original code embedded them inside
|
||||||
|
* setGroupItems(prev => ...) and we mirror that.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface FurniReducerContext {
|
||||||
|
isUnseen: (category: number, id: number) => boolean;
|
||||||
|
dispatchAdded: (id: number, type: number, category: number) => void;
|
||||||
|
fragments: { current: Map<number, FurnitureListItemParser>[] | null };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const applyFurnitureListAddOrUpdate = (
|
||||||
|
state: GroupItem[],
|
||||||
|
event: FurnitureListAddOrUpdateEvent,
|
||||||
|
ctx: FurniReducerContext
|
||||||
|
): GroupItem[] =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
const newValue = [ ...state ];
|
||||||
|
|
||||||
|
for(const item of parser.items)
|
||||||
|
{
|
||||||
|
let i = 0;
|
||||||
|
let groupItem: GroupItem = null;
|
||||||
|
|
||||||
|
while(i < newValue.length)
|
||||||
|
{
|
||||||
|
const group = newValue[i];
|
||||||
|
|
||||||
|
let j = 0;
|
||||||
|
|
||||||
|
while(j < group.items.length)
|
||||||
|
{
|
||||||
|
const furniture = group.items[j];
|
||||||
|
|
||||||
|
if(furniture.id === item.itemId)
|
||||||
|
{
|
||||||
|
furniture.update(item);
|
||||||
|
|
||||||
|
const newFurniture = [ ...group.items ];
|
||||||
|
|
||||||
|
newFurniture[j] = furniture;
|
||||||
|
|
||||||
|
group.items = newFurniture;
|
||||||
|
|
||||||
|
groupItem = group;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(groupItem) break;
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(groupItem)
|
||||||
|
{
|
||||||
|
groupItem.hasUnseenItems = true;
|
||||||
|
|
||||||
|
newValue[i] = CloneObject(groupItem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const furniture = new FurnitureItem(item);
|
||||||
|
|
||||||
|
addFurnitureItem(newValue, furniture, ctx.isUnseen(UnseenItemCategory.FURNI, item.itemId));
|
||||||
|
|
||||||
|
ctx.dispatchAdded(furniture.id, furniture.type, furniture.category);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const applyFurnitureList = (
|
||||||
|
state: GroupItem[],
|
||||||
|
event: FurnitureListEvent,
|
||||||
|
ctx: FurniReducerContext
|
||||||
|
): GroupItem[] =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
if(!ctx.fragments.current) ctx.fragments.current = new Array(parser.totalFragments);
|
||||||
|
|
||||||
|
const fragment = mergeFurniFragments(parser.fragment, parser.totalFragments, parser.fragmentNumber, ctx.fragments.current);
|
||||||
|
|
||||||
|
if(!fragment) return state;
|
||||||
|
|
||||||
|
const newValue = [ ...state ];
|
||||||
|
const existingIds = getAllItemIds(newValue);
|
||||||
|
|
||||||
|
for(const existingId of existingIds)
|
||||||
|
{
|
||||||
|
if(fragment.get(existingId)) continue;
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
while(index < newValue.length)
|
||||||
|
{
|
||||||
|
const group = newValue[index];
|
||||||
|
const item = group.remove(existingId);
|
||||||
|
|
||||||
|
if(!item)
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(getPlacingItemId() === item.ref)
|
||||||
|
{
|
||||||
|
cancelRoomObjectPlacement();
|
||||||
|
|
||||||
|
if(!attemptItemPlacement(group))
|
||||||
|
{
|
||||||
|
CreateLinkEvent('inventory/show');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(group.getTotalCount() <= 0)
|
||||||
|
{
|
||||||
|
newValue.splice(index, 1);
|
||||||
|
|
||||||
|
group.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const itemId of fragment.keys())
|
||||||
|
{
|
||||||
|
if(existingIds.indexOf(itemId) >= 0) continue;
|
||||||
|
|
||||||
|
const parserItem = fragment.get(itemId);
|
||||||
|
|
||||||
|
if(!parserItem) continue;
|
||||||
|
|
||||||
|
const item = new FurnitureItem(parserItem);
|
||||||
|
|
||||||
|
addFurnitureItem(newValue, item, ctx.isUnseen(UnseenItemCategory.FURNI, itemId));
|
||||||
|
|
||||||
|
ctx.dispatchAdded(item.id, item.type, item.category);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.fragments.current = null;
|
||||||
|
|
||||||
|
return newValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const applyFurnitureListRemoved = (
|
||||||
|
state: GroupItem[],
|
||||||
|
event: FurnitureListRemovedEvent
|
||||||
|
): GroupItem[] =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
const newValue = [ ...state ];
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
while(index < newValue.length)
|
||||||
|
{
|
||||||
|
const group = newValue[index];
|
||||||
|
const item = group.remove(parser.itemId);
|
||||||
|
|
||||||
|
if(!item)
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(getPlacingItemId() === item.ref)
|
||||||
|
{
|
||||||
|
cancelRoomObjectPlacement();
|
||||||
|
|
||||||
|
if(!attemptItemPlacement(group)) CreateLinkEvent('inventory/show');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(group.getTotalCount() <= 0)
|
||||||
|
{
|
||||||
|
newValue.splice(index, 1);
|
||||||
|
|
||||||
|
group.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clearUnseenFlags = (state: GroupItem[]): GroupItem[] =>
|
||||||
|
{
|
||||||
|
const newValue = [ ...state ];
|
||||||
|
|
||||||
|
for(const newGroup of newValue) newGroup.hasUnseenItems = false;
|
||||||
|
|
||||||
|
return newValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const refreshGroupItemsLocalization = (state: GroupItem[]): GroupItem[] =>
|
||||||
|
{
|
||||||
|
if(!state?.length) return state;
|
||||||
|
|
||||||
|
return state.map(groupItem =>
|
||||||
|
{
|
||||||
|
const nextGroupItem = groupItem.clone();
|
||||||
|
|
||||||
|
nextGroupItem.refreshLocalization();
|
||||||
|
|
||||||
|
return nextGroupItem;
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
import { CreateLinkEvent, FurnitureListAddOrUpdateEvent, FurnitureListComposer, FurnitureListEvent, FurnitureListInvalidateEvent, FurnitureListItemParser, FurnitureListRemovedEvent, FurniturePostItPlacedEvent } from '@nitrots/nitro-renderer';
|
import { FurnitureListAddOrUpdateEvent, FurnitureListComposer, FurnitureListEvent, FurnitureListInvalidateEvent, FurnitureListItemParser, FurnitureListRemovedEvent } from '@nitrots/nitro-renderer';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { useBetween } from 'use-between';
|
import { useBetween } from 'use-between';
|
||||||
import { CloneObject, DispatchUiEvent, FurnitureItem, GroupItem, SendMessageComposer, UnseenItemCategory, addFurnitureItem, attemptItemPlacement, cancelRoomObjectPlacement, getAllItemIds, getPlacingItemId, mergeFurniFragments } from '../../api';
|
import { DispatchUiEvent, GroupItem, SendMessageComposer, UnseenItemCategory } from '../../api';
|
||||||
import { InventoryFurniAddedEvent } from '../../events';
|
import { InventoryFurniAddedEvent } from '../../events';
|
||||||
import { useMessageEvent } from '../events';
|
import { useMessageEvent } from '../events';
|
||||||
import { useSharedVisibility } from '../useSharedVisibility';
|
import { useSharedVisibility } from '../useSharedVisibility';
|
||||||
import { useInventoryUnseenTracker } from './useInventoryUnseenTracker';
|
import { useInventoryUnseenTracker } from './useInventoryUnseenTracker';
|
||||||
|
import { applyFurnitureList, applyFurnitureListAddOrUpdate, applyFurnitureListRemoved, clearUnseenFlags, FurniReducerContext, refreshGroupItemsLocalization } from './useInventoryFurni.reducers';
|
||||||
let furniMsgFragments: Map<number, FurnitureListItemParser>[] = null;
|
|
||||||
|
|
||||||
const useInventoryFurniState = () =>
|
const useInventoryFurniState = () =>
|
||||||
{
|
{
|
||||||
const [ needsUpdate, setNeedsUpdate ] = useState(true);
|
const [ needsUpdate, setNeedsUpdate ] = useState(true);
|
||||||
const [ groupItems, setGroupItems ] = useState<GroupItem[]>([]);
|
const [ groupItems, setGroupItems ] = useState<GroupItem[]>([]);
|
||||||
const [ selectedItem, setSelectedItem ] = useState<GroupItem>(null);
|
const [ selectedItem, setSelectedItem ] = useState<GroupItem>(null);
|
||||||
|
const fragmentsRef = useRef<Map<number, FurnitureListItemParser>[] | null>(null);
|
||||||
const { isVisible = false, activate = null, deactivate = null } = useSharedVisibility();
|
const { isVisible = false, activate = null, deactivate = null } = useSharedVisibility();
|
||||||
const { isUnseen = null, resetCategory = null } = useInventoryUnseenTracker();
|
const { isUnseen = null, resetCategory = null } = useInventoryUnseenTracker();
|
||||||
|
|
||||||
@@ -52,199 +52,30 @@ const useInventoryFurniState = () =>
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const buildContext = (): FurniReducerContext => ({
|
||||||
|
isUnseen,
|
||||||
|
dispatchAdded: (id, type, category) => DispatchUiEvent(new InventoryFurniAddedEvent(id, type, category)),
|
||||||
|
fragments: fragmentsRef
|
||||||
|
});
|
||||||
|
|
||||||
useMessageEvent<FurnitureListAddOrUpdateEvent>(FurnitureListAddOrUpdateEvent, event =>
|
useMessageEvent<FurnitureListAddOrUpdateEvent>(FurnitureListAddOrUpdateEvent, event =>
|
||||||
{
|
{
|
||||||
const parser = event.getParser();
|
setGroupItems(prev => applyFurnitureListAddOrUpdate(prev, event, buildContext()));
|
||||||
|
|
||||||
setGroupItems(prevValue =>
|
|
||||||
{
|
|
||||||
const newValue = [ ...prevValue ];
|
|
||||||
|
|
||||||
for(const item of parser.items)
|
|
||||||
{
|
|
||||||
let i = 0;
|
|
||||||
let groupItem: GroupItem = null;
|
|
||||||
|
|
||||||
while(i < newValue.length)
|
|
||||||
{
|
|
||||||
const group = newValue[i];
|
|
||||||
|
|
||||||
let j = 0;
|
|
||||||
|
|
||||||
while(j < group.items.length)
|
|
||||||
{
|
|
||||||
const furniture = group.items[j];
|
|
||||||
|
|
||||||
if(furniture.id === item.itemId)
|
|
||||||
{
|
|
||||||
furniture.update(item);
|
|
||||||
|
|
||||||
const newFurniture = [ ...group.items ];
|
|
||||||
|
|
||||||
newFurniture[j] = furniture;
|
|
||||||
|
|
||||||
group.items = newFurniture;
|
|
||||||
|
|
||||||
groupItem = group;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
j++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(groupItem) break;
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(groupItem)
|
|
||||||
{
|
|
||||||
groupItem.hasUnseenItems = true;
|
|
||||||
|
|
||||||
newValue[i] = CloneObject(groupItem);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const furniture = new FurnitureItem(item);
|
|
||||||
|
|
||||||
addFurnitureItem(newValue, furniture, isUnseen(UnseenItemCategory.FURNI, item.itemId));
|
|
||||||
|
|
||||||
DispatchUiEvent(new InventoryFurniAddedEvent(furniture.id, furniture.type, furniture.category));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return newValue;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useMessageEvent<FurnitureListEvent>(FurnitureListEvent, event =>
|
useMessageEvent<FurnitureListEvent>(FurnitureListEvent, event =>
|
||||||
{
|
{
|
||||||
const parser = event.getParser();
|
setGroupItems(prev => applyFurnitureList(prev, event, buildContext()));
|
||||||
|
|
||||||
if(!furniMsgFragments) furniMsgFragments = new Array(parser.totalFragments);
|
|
||||||
|
|
||||||
const fragment = mergeFurniFragments(parser.fragment, parser.totalFragments, parser.fragmentNumber, furniMsgFragments);
|
|
||||||
|
|
||||||
if(!fragment) return;
|
|
||||||
|
|
||||||
setGroupItems(prevValue =>
|
|
||||||
{
|
|
||||||
const newValue = [ ...prevValue ];
|
|
||||||
const existingIds = getAllItemIds(newValue);
|
|
||||||
|
|
||||||
for(const existingId of existingIds)
|
|
||||||
{
|
|
||||||
if(fragment.get(existingId)) continue;
|
|
||||||
|
|
||||||
let index = 0;
|
|
||||||
|
|
||||||
while(index < newValue.length)
|
|
||||||
{
|
|
||||||
const group = newValue[index];
|
|
||||||
const item = group.remove(existingId);
|
|
||||||
|
|
||||||
if(!item)
|
|
||||||
{
|
|
||||||
index++;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(getPlacingItemId() === item.ref)
|
|
||||||
{
|
|
||||||
cancelRoomObjectPlacement();
|
|
||||||
|
|
||||||
if(!attemptItemPlacement(group))
|
|
||||||
{
|
|
||||||
CreateLinkEvent('inventory/show');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(group.getTotalCount() <= 0)
|
|
||||||
{
|
|
||||||
newValue.splice(index, 1);
|
|
||||||
|
|
||||||
group.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(const itemId of fragment.keys())
|
|
||||||
{
|
|
||||||
if(existingIds.indexOf(itemId) >= 0) continue;
|
|
||||||
|
|
||||||
const parser = fragment.get(itemId);
|
|
||||||
|
|
||||||
if(!parser) continue;
|
|
||||||
|
|
||||||
const item = new FurnitureItem(parser);
|
|
||||||
|
|
||||||
addFurnitureItem(newValue, item, isUnseen(UnseenItemCategory.FURNI, itemId));
|
|
||||||
|
|
||||||
DispatchUiEvent(new InventoryFurniAddedEvent(item.id, item.type, item.category));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return newValue;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
furniMsgFragments = null;
|
useMessageEvent<FurnitureListInvalidateEvent>(FurnitureListInvalidateEvent, () =>
|
||||||
});
|
|
||||||
|
|
||||||
useMessageEvent<FurnitureListInvalidateEvent>(FurnitureListInvalidateEvent, event =>
|
|
||||||
{
|
{
|
||||||
setNeedsUpdate(true);
|
setNeedsUpdate(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
useMessageEvent<FurnitureListRemovedEvent>(FurnitureListRemovedEvent, event =>
|
useMessageEvent<FurnitureListRemovedEvent>(FurnitureListRemovedEvent, event =>
|
||||||
{
|
{
|
||||||
const parser = event.getParser();
|
setGroupItems(prev => applyFurnitureListRemoved(prev, event));
|
||||||
|
|
||||||
setGroupItems(prevValue =>
|
|
||||||
{
|
|
||||||
const newValue = [ ...prevValue ];
|
|
||||||
|
|
||||||
let index = 0;
|
|
||||||
|
|
||||||
while(index < newValue.length)
|
|
||||||
{
|
|
||||||
const group = newValue[index];
|
|
||||||
const item = group.remove(parser.itemId);
|
|
||||||
|
|
||||||
if(!item)
|
|
||||||
{
|
|
||||||
index++;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(getPlacingItemId() === item.ref)
|
|
||||||
{
|
|
||||||
cancelRoomObjectPlacement();
|
|
||||||
|
|
||||||
if(!attemptItemPlacement(group)) CreateLinkEvent('inventory/show');
|
|
||||||
}
|
|
||||||
|
|
||||||
if(group.getTotalCount() <= 0)
|
|
||||||
{
|
|
||||||
newValue.splice(index, 1);
|
|
||||||
|
|
||||||
group.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return newValue;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
useMessageEvent<FurniturePostItPlacedEvent>(FurniturePostItPlacedEvent, event =>
|
|
||||||
{
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
@@ -271,14 +102,7 @@ const useInventoryFurniState = () =>
|
|||||||
{
|
{
|
||||||
if(resetCategory(UnseenItemCategory.FURNI))
|
if(resetCategory(UnseenItemCategory.FURNI))
|
||||||
{
|
{
|
||||||
setGroupItems(prevValue =>
|
setGroupItems(prev => clearUnseenFlags(prev));
|
||||||
{
|
|
||||||
const newValue = [ ...prevValue ];
|
|
||||||
|
|
||||||
for(const newGroup of newValue) newGroup.hasUnseenItems = false;
|
|
||||||
|
|
||||||
return newValue;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [ isVisible, resetCategory ]);
|
}, [ isVisible, resetCategory ]);
|
||||||
@@ -296,19 +120,7 @@ const useInventoryFurniState = () =>
|
|||||||
{
|
{
|
||||||
const refreshFurnitureLocalization = () =>
|
const refreshFurnitureLocalization = () =>
|
||||||
{
|
{
|
||||||
setGroupItems(prevValue =>
|
setGroupItems(prev => refreshGroupItemsLocalization(prev));
|
||||||
{
|
|
||||||
if(!prevValue?.length) return prevValue;
|
|
||||||
|
|
||||||
return prevValue.map(groupItem =>
|
|
||||||
{
|
|
||||||
const nextGroupItem = groupItem.clone();
|
|
||||||
|
|
||||||
nextGroupItem.refreshLocalization();
|
|
||||||
|
|
||||||
return nextGroupItem;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
setSelectedItem(prevValue =>
|
setSelectedItem(prevValue =>
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user