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 React, { useState, useRef, useContext, useCallback } from "react";
|
||||||
import { omit, isEmpty } from "../helpers/shared";
|
|
||||||
|
|
||||||
const MapLoadingContext = React.createContext();
|
const MapLoadingContext = React.createContext();
|
||||||
|
|
||||||
export function MapLoadingProvider({ children }) {
|
export function MapLoadingProvider({ children }) {
|
||||||
const [loadingAssetCount, setLoadingAssetCount] = useState(0);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
// Mapping from asset id to the count and total number of pieces loaded
|
||||||
function assetLoadStart() {
|
|
||||||
setLoadingAssetCount((prevLoadingAssets) => prevLoadingAssets + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function assetLoadFinish() {
|
|
||||||
setLoadingAssetCount((prevLoadingAssets) => prevLoadingAssets - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const assetProgressRef = useRef({});
|
const assetProgressRef = useRef({});
|
||||||
|
// Loading progress of all assets between 0 and 1
|
||||||
const loadingProgressRef = useRef(null);
|
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 = {
|
const value = {
|
||||||
assetLoadStart,
|
assetLoadStart,
|
||||||
assetLoadFinish,
|
|
||||||
isLoading,
|
isLoading,
|
||||||
assetProgressUpdate,
|
assetProgressUpdate,
|
||||||
loadingProgressRef,
|
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
|
* Custom send function with encoding, chunking and data channel support
|
||||||
sendObject(object, channel) {
|
* 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 {
|
try {
|
||||||
const packedData = encode(object);
|
const packedData = encode(object);
|
||||||
if (packedData.byteLength > MAX_BUFFER_SIZE) {
|
if (packedData.byteLength > MAX_BUFFER_SIZE) {
|
||||||
const chunks = this.chunk(packedData);
|
const chunks = this.chunk(packedData, chunkId);
|
||||||
for (let chunk of chunks) {
|
for (let chunk of chunks) {
|
||||||
if (this.dataChannels[channel]) {
|
if (this.dataChannels[channel]) {
|
||||||
this.dataChannels[channel].write(encode(chunk));
|
this.dataChannels[channel].write(encode(chunk));
|
||||||
@ -100,11 +105,17 @@ class Connection extends SimplePeer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Converted from https://github.com/peers/peerjs/
|
// 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 chunks = [];
|
||||||
const size = data.byteLength;
|
const size = data.byteLength;
|
||||||
const total = Math.ceil(size / MAX_BUFFER_SIZE);
|
const total = Math.ceil(size / MAX_BUFFER_SIZE);
|
||||||
const id = shortid.generate();
|
const id = chunkId || shortid.generate();
|
||||||
|
|
||||||
let index = 0;
|
let index = 0;
|
||||||
let start = 0;
|
let start = 0;
|
||||||
|
@ -39,12 +39,7 @@ function NetworkedMapAndTokens({ session }) {
|
|||||||
const { addToast } = useToasts();
|
const { addToast } = useToasts();
|
||||||
const { userId } = useAuth();
|
const { userId } = useAuth();
|
||||||
const partyState = useParty();
|
const partyState = useParty();
|
||||||
const {
|
const { assetLoadStart, assetProgressUpdate, isLoading } = useMapLoading();
|
||||||
assetLoadStart,
|
|
||||||
assetLoadFinish,
|
|
||||||
assetProgressUpdate,
|
|
||||||
isLoading,
|
|
||||||
} = useMapLoading();
|
|
||||||
|
|
||||||
const { updateMapState } = useMapData();
|
const { updateMapState } = useMapData();
|
||||||
const { getAsset, putAsset } = useAssets();
|
const { getAsset, putAsset } = useAssets();
|
||||||
@ -115,7 +110,7 @@ function NetworkedMapAndTokens({ session }) {
|
|||||||
const requestingAssetsRef = useRef(new Set());
|
const requestingAssetsRef = useRef(new Set());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!assetManifest) {
|
if (!assetManifest || !userId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,6 +127,9 @@ function NetworkedMapAndTokens({ session }) {
|
|||||||
(player) => player.userId === asset.owner
|
(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);
|
const cachedAsset = await getAsset(asset.id);
|
||||||
if (!owner) {
|
if (!owner) {
|
||||||
// Add no owner toast if we don't have asset in out cache
|
// 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
|
// TODO: Stop toast from appearing multiple times
|
||||||
addToast("Unable to find owner for asset");
|
addToast("Unable to find owner for asset");
|
||||||
}
|
}
|
||||||
|
requestingAssetsRef.current.delete(asset.id);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
requestingAssetsRef.current.add(asset.id);
|
|
||||||
|
|
||||||
if (cachedAsset) {
|
if (cachedAsset) {
|
||||||
requestingAssetsRef.current.delete(asset.id);
|
requestingAssetsRef.current.delete(asset.id);
|
||||||
} else {
|
} else {
|
||||||
session.sendTo(owner.sessionId, "assetRequest", asset.id);
|
session.sendTo(owner.sessionId, "assetRequest", asset.id);
|
||||||
|
assetLoadStart(asset.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
requestAssetsIfNeeded();
|
requestAssetsIfNeeded();
|
||||||
}, [assetManifest, partyState, session, userId, addToast, getAsset]);
|
}, [
|
||||||
|
assetManifest,
|
||||||
|
partyState,
|
||||||
|
session,
|
||||||
|
userId,
|
||||||
|
addToast,
|
||||||
|
getAsset,
|
||||||
|
assetLoadStart,
|
||||||
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map state
|
* Map state
|
||||||
@ -376,21 +382,16 @@ function NetworkedMapAndTokens({ session }) {
|
|||||||
async function handlePeerData({ id, data, reply }) {
|
async function handlePeerData({ id, data, reply }) {
|
||||||
if (id === "assetRequest") {
|
if (id === "assetRequest") {
|
||||||
const asset = await getAsset(data);
|
const asset = await getAsset(data);
|
||||||
reply("assetResponse", asset);
|
reply("assetResponse", asset, undefined, asset.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id === "assetResponse") {
|
if (id === "assetResponse") {
|
||||||
await putAsset(data);
|
await putAsset(data);
|
||||||
requestingAssetsRef.current.delete(data.id);
|
requestingAssetsRef.current.delete(data.id);
|
||||||
assetLoadFinish();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePeerDataProgress({ id, total, count }) {
|
function handlePeerDataProgress({ id, total, count }) {
|
||||||
if (count === 1) {
|
|
||||||
// Corresponding asset load finished called in asset response
|
|
||||||
assetLoadStart();
|
|
||||||
}
|
|
||||||
assetProgressUpdate({ id, total, count });
|
assetProgressUpdate({ id, total, count });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,8 @@ import { logError } from "../helpers/logging";
|
|||||||
* @callback peerReply
|
* @callback peerReply
|
||||||
* @param {string} id - The id of the event
|
* @param {string} id - The id of the event
|
||||||
* @param {object} data - The data to send
|
* @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} sessionId - The socket id of the player to send to
|
||||||
* @param {string} eventId - The id of the event to send
|
* @param {string} eventId - The id of the event to send
|
||||||
* @param {object} data
|
* @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 (!(sessionId in this.peers)) {
|
||||||
if (!this._addPeer(sessionId, true)) {
|
if (!this._addPeer(sessionId, true)) {
|
||||||
return;
|
return;
|
||||||
@ -133,7 +135,8 @@ class Session extends EventEmitter {
|
|||||||
this.peers[sessionId].connection.once("connect", () => {
|
this.peers[sessionId].connection.once("connect", () => {
|
||||||
this.peers[sessionId].connection.sendObject(
|
this.peers[sessionId].connection.sendObject(
|
||||||
{ id: eventId, data },
|
{ id: eventId, data },
|
||||||
channel
|
channel,
|
||||||
|
chunkId
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -226,8 +229,8 @@ class Session extends EventEmitter {
|
|||||||
|
|
||||||
const peer = { id, connection, initiator, ready: false };
|
const peer = { id, connection, initiator, ready: false };
|
||||||
|
|
||||||
function sendPeer(id, data, channel) {
|
function reply(id, data, channel, chunkId) {
|
||||||
peer.connection.sendObject({ id, data }, channel);
|
peer.connection.sendObject({ id, data }, channel, chunkId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSignal(signal) {
|
function handleSignal(signal) {
|
||||||
@ -246,7 +249,7 @@ class Session extends EventEmitter {
|
|||||||
* @property {SessionPeer} peer
|
* @property {SessionPeer} peer
|
||||||
* @property {peerReply} reply
|
* @property {peerReply} reply
|
||||||
*/
|
*/
|
||||||
this.emit("peerConnect", { peer, reply: sendPeer });
|
this.emit("peerConnect", { peer, reply });
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDataComplete(data) {
|
function handleDataComplete(data) {
|
||||||
@ -264,7 +267,7 @@ class Session extends EventEmitter {
|
|||||||
peer,
|
peer,
|
||||||
id: data.id,
|
id: data.id,
|
||||||
data: data.data,
|
data: data.data,
|
||||||
reply: sendPeer,
|
reply,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,7 +277,7 @@ class Session extends EventEmitter {
|
|||||||
id,
|
id,
|
||||||
count,
|
count,
|
||||||
total,
|
total,
|
||||||
reply: sendPeer,
|
reply,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user