Changed asset loading bar to show progress as a group of assets
This commit is contained in:
parent
994b9b5ebb
commit
0ccee84cbf
@ -1,46 +1,44 @@
|
||||
import React, { useState, useRef, useContext } from "react";
|
||||
import { omit, isEmpty } from "../helpers/shared";
|
||||
import React, { useState, useRef, useContext, useCallback } from "react";
|
||||
|
||||
const MapLoadingContext = React.createContext();
|
||||
|
||||
export function MapLoadingProvider({ children }) {
|
||||
const [loadingAssetCount, setLoadingAssetCount] = useState(0);
|
||||
|
||||
function assetLoadStart() {
|
||||
setLoadingAssetCount((prevLoadingAssets) => prevLoadingAssets + 1);
|
||||
}
|
||||
|
||||
function assetLoadFinish() {
|
||||
setLoadingAssetCount((prevLoadingAssets) => prevLoadingAssets - 1);
|
||||
}
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
// Mapping from asset id to the count and total number of pieces loaded
|
||||
const assetProgressRef = useRef({});
|
||||
// Loading progress of all assets between 0 and 1
|
||||
const loadingProgressRef = useRef(null);
|
||||
function assetProgressUpdate({ id, count, total }) {
|
||||
if (count === total) {
|
||||
assetProgressRef.current = omit(assetProgressRef.current, [id]);
|
||||
} else {
|
||||
assetProgressRef.current = {
|
||||
...assetProgressRef.current,
|
||||
[id]: { count, total },
|
||||
};
|
||||
}
|
||||
if (!isEmpty(assetProgressRef.current)) {
|
||||
let total = 0;
|
||||
let count = 0;
|
||||
for (let progress of Object.values(assetProgressRef.current)) {
|
||||
total += progress.total;
|
||||
count += progress.count;
|
||||
}
|
||||
loadingProgressRef.current = count / total;
|
||||
}
|
||||
}
|
||||
|
||||
const isLoading = loadingAssetCount > 0;
|
||||
const assetLoadStart = useCallback((id) => {
|
||||
setIsLoading(true);
|
||||
// Add asset at a 0% progress
|
||||
assetProgressRef.current = {
|
||||
...assetProgressRef.current,
|
||||
[id]: { count: 0, total: 1 },
|
||||
};
|
||||
}, []);
|
||||
|
||||
const assetProgressUpdate = useCallback(({ id, count, total }) => {
|
||||
assetProgressRef.current = {
|
||||
...assetProgressRef.current,
|
||||
[id]: { count, total },
|
||||
};
|
||||
// Update loading progress
|
||||
let complete = 0;
|
||||
const progresses = Object.values(assetProgressRef.current);
|
||||
for (let progress of progresses) {
|
||||
complete += progress.count / progress.total;
|
||||
}
|
||||
loadingProgressRef.current = complete / progresses.length;
|
||||
// All loading is complete
|
||||
if (loadingProgressRef.current === 1) {
|
||||
setIsLoading(false);
|
||||
assetProgressRef.current = {};
|
||||
}
|
||||
}, []);
|
||||
|
||||
const value = {
|
||||
assetLoadStart,
|
||||
assetLoadFinish,
|
||||
isLoading,
|
||||
assetProgressUpdate,
|
||||
loadingProgressRef,
|
||||
|
@ -55,13 +55,18 @@ class Connection extends SimplePeer {
|
||||
}
|
||||
}
|
||||
|
||||
// Custom send function with encoding, chunking and data channel support
|
||||
// Uses `write` to send the data to allow for buffer / backpressure handling
|
||||
sendObject(object, channel) {
|
||||
/**
|
||||
* Custom send function with encoding, chunking and data channel support
|
||||
* Uses `write` to send the data to allow for buffer / backpressure handling
|
||||
* @param {any} object
|
||||
* @param {string=} channel
|
||||
* @param {string=} chunkId Optional ID to use for chunking
|
||||
*/
|
||||
sendObject(object, channel, chunkId) {
|
||||
try {
|
||||
const packedData = encode(object);
|
||||
if (packedData.byteLength > MAX_BUFFER_SIZE) {
|
||||
const chunks = this.chunk(packedData);
|
||||
const chunks = this.chunk(packedData, chunkId);
|
||||
for (let chunk of chunks) {
|
||||
if (this.dataChannels[channel]) {
|
||||
this.dataChannels[channel].write(encode(chunk));
|
||||
@ -100,11 +105,17 @@ class Connection extends SimplePeer {
|
||||
}
|
||||
|
||||
// Converted from https://github.com/peers/peerjs/
|
||||
chunk(data) {
|
||||
/**
|
||||
* Chunk byte array
|
||||
* @param {Uint8Array} data
|
||||
* @param {string=} chunkId
|
||||
* @returns {Uint8Array[]}
|
||||
*/
|
||||
chunk(data, chunkId) {
|
||||
const chunks = [];
|
||||
const size = data.byteLength;
|
||||
const total = Math.ceil(size / MAX_BUFFER_SIZE);
|
||||
const id = shortid.generate();
|
||||
const id = chunkId || shortid.generate();
|
||||
|
||||
let index = 0;
|
||||
let start = 0;
|
||||
|
@ -39,12 +39,7 @@ function NetworkedMapAndTokens({ session }) {
|
||||
const { addToast } = useToasts();
|
||||
const { userId } = useAuth();
|
||||
const partyState = useParty();
|
||||
const {
|
||||
assetLoadStart,
|
||||
assetLoadFinish,
|
||||
assetProgressUpdate,
|
||||
isLoading,
|
||||
} = useMapLoading();
|
||||
const { assetLoadStart, assetProgressUpdate, isLoading } = useMapLoading();
|
||||
|
||||
const { updateMapState } = useMapData();
|
||||
const { getAsset, putAsset } = useAssets();
|
||||
@ -115,7 +110,7 @@ function NetworkedMapAndTokens({ session }) {
|
||||
const requestingAssetsRef = useRef(new Set());
|
||||
|
||||
useEffect(() => {
|
||||
if (!assetManifest) {
|
||||
if (!assetManifest || !userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -132,6 +127,9 @@ function NetworkedMapAndTokens({ session }) {
|
||||
(player) => player.userId === asset.owner
|
||||
);
|
||||
|
||||
// Ensure requests are added before any async operation to prevent them from sending twice
|
||||
requestingAssetsRef.current.add(asset.id);
|
||||
|
||||
const cachedAsset = await getAsset(asset.id);
|
||||
if (!owner) {
|
||||
// Add no owner toast if we don't have asset in out cache
|
||||
@ -139,21 +137,29 @@ function NetworkedMapAndTokens({ session }) {
|
||||
// TODO: Stop toast from appearing multiple times
|
||||
addToast("Unable to find owner for asset");
|
||||
}
|
||||
requestingAssetsRef.current.delete(asset.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
requestingAssetsRef.current.add(asset.id);
|
||||
|
||||
if (cachedAsset) {
|
||||
requestingAssetsRef.current.delete(asset.id);
|
||||
} else {
|
||||
session.sendTo(owner.sessionId, "assetRequest", asset.id);
|
||||
assetLoadStart(asset.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
requestAssetsIfNeeded();
|
||||
}, [assetManifest, partyState, session, userId, addToast, getAsset]);
|
||||
}, [
|
||||
assetManifest,
|
||||
partyState,
|
||||
session,
|
||||
userId,
|
||||
addToast,
|
||||
getAsset,
|
||||
assetLoadStart,
|
||||
]);
|
||||
|
||||
/**
|
||||
* Map state
|
||||
@ -376,21 +382,16 @@ function NetworkedMapAndTokens({ session }) {
|
||||
async function handlePeerData({ id, data, reply }) {
|
||||
if (id === "assetRequest") {
|
||||
const asset = await getAsset(data);
|
||||
reply("assetResponse", asset);
|
||||
reply("assetResponse", asset, undefined, asset.id);
|
||||
}
|
||||
|
||||
if (id === "assetResponse") {
|
||||
await putAsset(data);
|
||||
requestingAssetsRef.current.delete(data.id);
|
||||
assetLoadFinish();
|
||||
}
|
||||
}
|
||||
|
||||
function handlePeerDataProgress({ id, total, count }) {
|
||||
if (count === 1) {
|
||||
// Corresponding asset load finished called in asset response
|
||||
assetLoadStart();
|
||||
}
|
||||
assetProgressUpdate({ id, total, count });
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,8 @@ import { logError } from "../helpers/logging";
|
||||
* @callback peerReply
|
||||
* @param {string} id - The id of the event
|
||||
* @param {object} data - The data to send
|
||||
* @param {string} channel - The channel to send to
|
||||
* @param {string=} channel - The channel to send to
|
||||
* @param {string=} chunkId
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -120,9 +121,10 @@ class Session extends EventEmitter {
|
||||
* @param {string} sessionId - The socket id of the player to send to
|
||||
* @param {string} eventId - The id of the event to send
|
||||
* @param {object} data
|
||||
* @param {string} channel
|
||||
* @param {string=} channel
|
||||
* @param {string=} chunkId
|
||||
*/
|
||||
sendTo(sessionId, eventId, data, channel) {
|
||||
sendTo(sessionId, eventId, data, channel, chunkId) {
|
||||
if (!(sessionId in this.peers)) {
|
||||
if (!this._addPeer(sessionId, true)) {
|
||||
return;
|
||||
@ -133,7 +135,8 @@ class Session extends EventEmitter {
|
||||
this.peers[sessionId].connection.once("connect", () => {
|
||||
this.peers[sessionId].connection.sendObject(
|
||||
{ id: eventId, data },
|
||||
channel
|
||||
channel,
|
||||
chunkId
|
||||
);
|
||||
});
|
||||
} else {
|
||||
@ -226,8 +229,8 @@ class Session extends EventEmitter {
|
||||
|
||||
const peer = { id, connection, initiator, ready: false };
|
||||
|
||||
function sendPeer(id, data, channel) {
|
||||
peer.connection.sendObject({ id, data }, channel);
|
||||
function reply(id, data, channel, chunkId) {
|
||||
peer.connection.sendObject({ id, data }, channel, chunkId);
|
||||
}
|
||||
|
||||
function handleSignal(signal) {
|
||||
@ -246,7 +249,7 @@ class Session extends EventEmitter {
|
||||
* @property {SessionPeer} peer
|
||||
* @property {peerReply} reply
|
||||
*/
|
||||
this.emit("peerConnect", { peer, reply: sendPeer });
|
||||
this.emit("peerConnect", { peer, reply });
|
||||
}
|
||||
|
||||
function handleDataComplete(data) {
|
||||
@ -264,7 +267,7 @@ class Session extends EventEmitter {
|
||||
peer,
|
||||
id: data.id,
|
||||
data: data.data,
|
||||
reply: sendPeer,
|
||||
reply,
|
||||
});
|
||||
}
|
||||
|
||||
@ -274,7 +277,7 @@ class Session extends EventEmitter {
|
||||
id,
|
||||
count,
|
||||
total,
|
||||
reply: sendPeer,
|
||||
reply,
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user