mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +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 { useEffect, useState } from 'react';
|
||||
import { FurnitureListAddOrUpdateEvent, FurnitureListComposer, FurnitureListEvent, FurnitureListInvalidateEvent, FurnitureListItemParser, FurnitureListRemovedEvent } from '@nitrots/nitro-renderer';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
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 { useMessageEvent } from '../events';
|
||||
import { useSharedVisibility } from '../useSharedVisibility';
|
||||
import { useInventoryUnseenTracker } from './useInventoryUnseenTracker';
|
||||
|
||||
let furniMsgFragments: Map<number, FurnitureListItemParser>[] = null;
|
||||
import { applyFurnitureList, applyFurnitureListAddOrUpdate, applyFurnitureListRemoved, clearUnseenFlags, FurniReducerContext, refreshGroupItemsLocalization } from './useInventoryFurni.reducers';
|
||||
|
||||
const useInventoryFurniState = () =>
|
||||
{
|
||||
const [ needsUpdate, setNeedsUpdate ] = useState(true);
|
||||
const [ groupItems, setGroupItems ] = useState<GroupItem[]>([]);
|
||||
const [ selectedItem, setSelectedItem ] = useState<GroupItem>(null);
|
||||
const fragmentsRef = useRef<Map<number, FurnitureListItemParser>[] | null>(null);
|
||||
const { isVisible = false, activate = null, deactivate = null } = useSharedVisibility();
|
||||
const { isUnseen = null, resetCategory = null } = useInventoryUnseenTracker();
|
||||
|
||||
@@ -52,199 +52,30 @@ const useInventoryFurniState = () =>
|
||||
return null;
|
||||
};
|
||||
|
||||
const buildContext = (): FurniReducerContext => ({
|
||||
isUnseen,
|
||||
dispatchAdded: (id, type, category) => DispatchUiEvent(new InventoryFurniAddedEvent(id, type, category)),
|
||||
fragments: fragmentsRef
|
||||
});
|
||||
|
||||
useMessageEvent<FurnitureListAddOrUpdateEvent>(FurnitureListAddOrUpdateEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
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;
|
||||
});
|
||||
setGroupItems(prev => applyFurnitureListAddOrUpdate(prev, event, buildContext()));
|
||||
});
|
||||
|
||||
useMessageEvent<FurnitureListEvent>(FurnitureListEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
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;
|
||||
setGroupItems(prev => applyFurnitureList(prev, event, buildContext()));
|
||||
});
|
||||
|
||||
useMessageEvent<FurnitureListInvalidateEvent>(FurnitureListInvalidateEvent, event =>
|
||||
useMessageEvent<FurnitureListInvalidateEvent>(FurnitureListInvalidateEvent, () =>
|
||||
{
|
||||
setNeedsUpdate(true);
|
||||
});
|
||||
|
||||
useMessageEvent<FurnitureListRemovedEvent>(FurnitureListRemovedEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
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 =>
|
||||
{
|
||||
|
||||
setGroupItems(prev => applyFurnitureListRemoved(prev, event));
|
||||
});
|
||||
|
||||
useEffect(() =>
|
||||
@@ -271,14 +102,7 @@ const useInventoryFurniState = () =>
|
||||
{
|
||||
if(resetCategory(UnseenItemCategory.FURNI))
|
||||
{
|
||||
setGroupItems(prevValue =>
|
||||
{
|
||||
const newValue = [ ...prevValue ];
|
||||
|
||||
for(const newGroup of newValue) newGroup.hasUnseenItems = false;
|
||||
|
||||
return newValue;
|
||||
});
|
||||
setGroupItems(prev => clearUnseenFlags(prev));
|
||||
}
|
||||
};
|
||||
}, [ isVisible, resetCategory ]);
|
||||
@@ -296,19 +120,7 @@ const useInventoryFurniState = () =>
|
||||
{
|
||||
const refreshFurnitureLocalization = () =>
|
||||
{
|
||||
setGroupItems(prevValue =>
|
||||
{
|
||||
if(!prevValue?.length) return prevValue;
|
||||
|
||||
return prevValue.map(groupItem =>
|
||||
{
|
||||
const nextGroupItem = groupItem.clone();
|
||||
|
||||
nextGroupItem.refreshLocalization();
|
||||
|
||||
return nextGroupItem;
|
||||
});
|
||||
});
|
||||
setGroupItems(prev => refreshGroupItemsLocalization(prev));
|
||||
|
||||
setSelectedItem(prevValue =>
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user