mirror of
https://codeberg.org/demostf/frontend.git
synced 2026-06-03 18:24:12 +02:00
fix sync
This commit is contained in:
parent
8f9eeadb74
commit
b08267a08c
10 changed files with 318 additions and 304 deletions
|
|
@ -1,26 +1,29 @@
|
|||
export interface AnalyseMenuProps {
|
||||
sessionName: string;
|
||||
onShare: Function;
|
||||
canShare: boolean;
|
||||
isShared: boolean;
|
||||
sessionName: string;
|
||||
onShare: Function;
|
||||
canShare: boolean;
|
||||
isShared: boolean;
|
||||
}
|
||||
|
||||
export function AnalyseMenu({sessionName, onShare, canShare, isShared}:AnalyseMenuProps) {
|
||||
export function AnalyseMenu(props: AnalyseMenuProps) {
|
||||
const loc = () => window.location.toString().replace(/\#.*/, '') + '#' + props.sessionName;
|
||||
const shareText = () => (props.isShared) ?
|
||||
<input class="share-text" value={loc()} readOnly={true}
|
||||
title="Use this link to join the current session"
|
||||
style={{width: `${(loc().length * 8)}px`}}
|
||||
onFocus={(event) => {
|
||||
(event.target as HTMLInputElement).select()
|
||||
}}/> : '';
|
||||
|
||||
const loc = window.location.toString().replace(/\#.+/, '') + '#' + sessionName;
|
||||
const shareText = (isShared) ?
|
||||
<input class="share-text" value={loc} readOnly={true}
|
||||
title="Use this link to join the current session"
|
||||
style={{width: `${(loc.length*8)}px`}}
|
||||
onFocus={(event)=>{(event.target as HTMLInputElement).select()}}/> : '';
|
||||
const shareButton = () => (props.canShare) ? <div class="analyse-menu">
|
||||
<button class="share-session" title="Start a shared session"
|
||||
onClick={() => {
|
||||
props.onShare()
|
||||
}}/>
|
||||
{shareText}
|
||||
</div> : '';
|
||||
|
||||
const shareButton = (canShare) ? <div class="analyse-menu">
|
||||
<button class="share-session" title="Start a shared session"
|
||||
onClick={()=>{onShare()}}/>
|
||||
{shareText}
|
||||
</div>: '';
|
||||
|
||||
return (<div>
|
||||
{shareButton}
|
||||
</div>)
|
||||
return (<div>
|
||||
{shareButton}
|
||||
</div>)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,178 +12,182 @@ import {Session, StateUpdate} from "./Session";
|
|||
import {DemoHead} from "../../header";
|
||||
|
||||
export interface AnalyseProps {
|
||||
header: DemoHead;
|
||||
isStored: boolean;
|
||||
parser: AsyncParser;
|
||||
header: DemoHead;
|
||||
isStored: boolean;
|
||||
parser: AsyncParser;
|
||||
}
|
||||
|
||||
export const Analyser = (props: AnalyseProps) => {
|
||||
const parser = props.parser;
|
||||
const intervalPerTick = props.header.duration / props.header.ticks;
|
||||
const parser = props.parser;
|
||||
const intervalPerTick = props.header.duration / props.header.ticks;
|
||||
|
||||
const [tick, setTick] = createSignal<number>(0);
|
||||
const [scale, setScale] = createSignal<number>(1);
|
||||
const [playing, setPlaying] = createSignal<boolean>(false);
|
||||
const [sessionName, setSessionName] = createSignal<string>("");
|
||||
const [tick, setTick] = createSignal<number>(0);
|
||||
const [scale, setScale] = createSignal<number>(1);
|
||||
const [playing, setPlaying] = createSignal<boolean>(false);
|
||||
const [sessionName, setSessionName] = createSignal<string>("");
|
||||
|
||||
let lastFrameTime = 0;
|
||||
let playStartTick = 0;
|
||||
let playStartTime = 0;
|
||||
let lastFrameTime = 0;
|
||||
let playStartTick = 0;
|
||||
let playStartTime = 0;
|
||||
|
||||
const onUpdate = (update: StateUpdate) => {
|
||||
if (update["tick"]) {
|
||||
setTick(update["tick"]);
|
||||
}
|
||||
if (update["playing"]) {
|
||||
setPlaying(update["playing"]);
|
||||
}
|
||||
}
|
||||
const onUpdate = (update: StateUpdate) => {
|
||||
if (update["tick"]) {
|
||||
setTick(update["tick"]);
|
||||
}
|
||||
if (update.hasOwnProperty("playing")) {
|
||||
if (update["playing"]) {
|
||||
play();
|
||||
} else {
|
||||
pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let session: Session | null = null;
|
||||
if (props.isStored && window.location.hash) {
|
||||
const parsed = parseInt(window.location.hash.substr(1), 10);
|
||||
if (('#' + parsed) === window.location.hash) {
|
||||
setTick(Math.floor(parsed));
|
||||
} else {
|
||||
const name = window.location.hash.substring(1);
|
||||
Session.join(name, onUpdate);
|
||||
setSessionName(name);
|
||||
}
|
||||
}
|
||||
let session: Session | null = null;
|
||||
if (props.isStored && window.location.hash) {
|
||||
const parsed = parseInt(window.location.hash.substr(1), 10);
|
||||
if (('#' + parsed) === window.location.hash) {
|
||||
setTick(Math.floor(parsed));
|
||||
} else {
|
||||
const name = window.location.hash.substring(1);
|
||||
session = Session.join(name, onUpdate);
|
||||
setSessionName(name);
|
||||
}
|
||||
}
|
||||
|
||||
const map = parser.demo.header.map;
|
||||
const backgroundBoundaries = getMapBoundaries(map);
|
||||
if (!backgroundBoundaries) {
|
||||
throw new Error(`Map not supported "${map}".`);
|
||||
}
|
||||
const worldSize = {
|
||||
width: backgroundBoundaries.boundary_max.x - backgroundBoundaries.boundary_min.x,
|
||||
height: backgroundBoundaries.boundary_max.y - backgroundBoundaries.boundary_min.y,
|
||||
};
|
||||
const map = parser.demo.header.map;
|
||||
const backgroundBoundaries = getMapBoundaries(map);
|
||||
if (!backgroundBoundaries) {
|
||||
throw new Error(`Map not supported "${map}".`);
|
||||
}
|
||||
const worldSize = {
|
||||
width: backgroundBoundaries.boundary_max.x - backgroundBoundaries.boundary_min.x,
|
||||
height: backgroundBoundaries.boundary_max.y - backgroundBoundaries.boundary_min.y,
|
||||
};
|
||||
|
||||
const setTickNow = (tick) => {
|
||||
lastFrameTime = 0;
|
||||
playStartTick = tick;
|
||||
playStartTime = window.performance.now();
|
||||
setTick(tick);
|
||||
setHash(tick);
|
||||
if (session) {
|
||||
session.update({tick});
|
||||
}
|
||||
}
|
||||
const setTickNow = (tick) => {
|
||||
lastFrameTime = 0;
|
||||
playStartTick = tick;
|
||||
playStartTime = window.performance.now();
|
||||
setTick(tick);
|
||||
setHash(tick);
|
||||
if (session) {
|
||||
session.update({tick});
|
||||
}
|
||||
}
|
||||
|
||||
const pause = () => {
|
||||
setPlaying(false);
|
||||
lastFrameTime = 0;
|
||||
if (session) {
|
||||
session.update({playing: false});
|
||||
}
|
||||
}
|
||||
const pause = () => {
|
||||
setPlaying(false);
|
||||
lastFrameTime = 0;
|
||||
if (session) {
|
||||
session.update({playing: false});
|
||||
}
|
||||
}
|
||||
|
||||
const play = () => {
|
||||
playStartTick = tick();
|
||||
playStartTime = window.performance.now();
|
||||
setPlaying(true);
|
||||
requestAnimationFrame(animFrame);
|
||||
if (session) {
|
||||
session.update({playing: false});
|
||||
}
|
||||
}
|
||||
const play = () => {
|
||||
playStartTick = tick();
|
||||
playStartTime = window.performance.now();
|
||||
setPlaying(true);
|
||||
requestAnimationFrame(animFrame);
|
||||
if (session) {
|
||||
session.update({playing: true});
|
||||
}
|
||||
}
|
||||
|
||||
const togglePlay = () => {
|
||||
if (playing()) {
|
||||
pause();
|
||||
} else {
|
||||
play();
|
||||
}
|
||||
}
|
||||
const togglePlay = () => {
|
||||
if (playing()) {
|
||||
pause();
|
||||
} else {
|
||||
play();
|
||||
}
|
||||
}
|
||||
|
||||
const syncPlayTick = debounce(500, () => {
|
||||
if (session) {
|
||||
session.update({
|
||||
playing: playing(),
|
||||
tick: tick(),
|
||||
});
|
||||
}
|
||||
});
|
||||
const syncPlayTick = throttle(2500, () => {
|
||||
if (session) {
|
||||
session.update({
|
||||
playing: playing(),
|
||||
tick: tick(),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const setHash = debounce(250, (tick) => {
|
||||
if (!session && props.isStored) {
|
||||
history.replaceState('', '', '#' + tick);
|
||||
}
|
||||
});
|
||||
const setHash = debounce(250, (tick) => {
|
||||
if (!session && props.isStored) {
|
||||
history.replaceState('', '', '#' + tick);
|
||||
}
|
||||
});
|
||||
|
||||
const animFrame = (timestamp:number) => {
|
||||
const timePassed = (timestamp - playStartTime) / 1000;
|
||||
const targetTick = playStartTick + (Math.round(timePassed / intervalPerTick));
|
||||
lastFrameTime = timestamp;
|
||||
if (targetTick >= (parser.demo.tick - 1)) {
|
||||
pause();
|
||||
}
|
||||
setHash(targetTick);
|
||||
setTick(targetTick);
|
||||
syncPlayTick();
|
||||
const animFrame = (timestamp: number) => {
|
||||
const timePassed = (timestamp - playStartTime) / 1000;
|
||||
const targetTick = playStartTick + (Math.round(timePassed / intervalPerTick));
|
||||
lastFrameTime = timestamp;
|
||||
if (targetTick >= (parser.demo.tick - 1)) {
|
||||
pause();
|
||||
}
|
||||
setHash(targetTick);
|
||||
setTick(targetTick);
|
||||
syncPlayTick();
|
||||
|
||||
if (playing()) {
|
||||
requestAnimationFrame(animFrame);
|
||||
}
|
||||
}
|
||||
if (playing()) {
|
||||
requestAnimationFrame(animFrame);
|
||||
}
|
||||
}
|
||||
|
||||
const players = () => parser.getPlayersAtTick(tick());
|
||||
const buildings = () => parser.getBuildingsAtTick(tick());
|
||||
const kills = parser.getKills();
|
||||
const playButtonText = () => (playing()) ? '⏸' : '▶️';
|
||||
const disabled = session && !session.isOwner();
|
||||
const isShared = () => sessionName() !== '';
|
||||
const players = () => parser.getPlayersAtTick(tick());
|
||||
const buildings = () => parser.getBuildingsAtTick(tick());
|
||||
const kills = parser.getKills();
|
||||
const playButtonText = () => (playing()) ? '⏸' : '▶️';
|
||||
const disabled = session && !session.isOwner();
|
||||
const isShared = () => sessionName() !== '';
|
||||
|
||||
console.log(intervalPerTick);
|
||||
console.log(intervalPerTick);
|
||||
|
||||
const timeTitle = () => `${tickToTime(tick(), intervalPerTick)} (tick ${tick()})`;
|
||||
const timeTitle = () => `${tickToTime(tick(), intervalPerTick)} (tick ${tick()})`;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div class="map-holder">
|
||||
<MapContainer contentSize={worldSize}
|
||||
onScale={setScale}>
|
||||
<MapRender size={worldSize}
|
||||
players={players()}
|
||||
buildings={buildings()}
|
||||
header={props.header}
|
||||
world={backgroundBoundaries}
|
||||
scale={scale()}/>
|
||||
</MapContainer>
|
||||
<AnalyseMenu sessionName={sessionName()}
|
||||
onShare={() => {
|
||||
session = Session.create({
|
||||
tick: tick(),
|
||||
playing: playing()
|
||||
});
|
||||
setSessionName(session.sessionName);
|
||||
}}
|
||||
canShare={props.isStored && !disabled}
|
||||
isShared={isShared()}
|
||||
/>
|
||||
<SpecHUD parser={parser} tick={tick()}
|
||||
players={players()} kills={kills}/>
|
||||
</div>
|
||||
<div class="time-control"
|
||||
title={timeTitle()}>
|
||||
<input class="play-pause-button" type="button"
|
||||
value={playButtonText()}
|
||||
disabled={disabled}
|
||||
onClick={togglePlay}
|
||||
/>
|
||||
<Timeline parser={parser} tick={tick()}
|
||||
onSetTick={throttle(50, (tick) => {
|
||||
setTickNow(tick);
|
||||
})}
|
||||
disabled={disabled}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<div class="map-holder">
|
||||
<MapContainer contentSize={worldSize}
|
||||
onScale={setScale}>
|
||||
<MapRender size={worldSize}
|
||||
players={players()}
|
||||
buildings={buildings()}
|
||||
header={props.header}
|
||||
world={backgroundBoundaries}
|
||||
scale={scale()}/>
|
||||
</MapContainer>
|
||||
<AnalyseMenu sessionName={sessionName()}
|
||||
onShare={() => {
|
||||
session = Session.create({
|
||||
tick: tick(),
|
||||
playing: playing()
|
||||
});
|
||||
setSessionName(session.sessionName);
|
||||
}}
|
||||
canShare={props.isStored && !disabled}
|
||||
isShared={isShared()}
|
||||
/>
|
||||
<SpecHUD parser={parser} tick={tick()}
|
||||
players={players()} kills={kills}/>
|
||||
</div>
|
||||
<div class="time-control"
|
||||
title={timeTitle()}>
|
||||
<input class="play-pause-button" type="button"
|
||||
value={playButtonText()}
|
||||
disabled={disabled}
|
||||
onClick={togglePlay}
|
||||
/>
|
||||
<Timeline parser={parser} tick={tick()}
|
||||
onSetTick={throttle(50, (tick) => {
|
||||
setTickNow(tick);
|
||||
})}
|
||||
disabled={disabled}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function tickToTime(tick: number, intervalPerTick: number): string {
|
||||
let seconds = Math.floor(tick * intervalPerTick);
|
||||
return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, '0')}`;
|
||||
let seconds = Math.floor(tick * intervalPerTick);
|
||||
return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, '0')}`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export class Session {
|
|||
this.sessionName = name;
|
||||
this.initialState = initialState;
|
||||
this.onState = onState;
|
||||
this.open();
|
||||
}
|
||||
|
||||
public static create(state: PlaybackState): Session {
|
||||
|
|
@ -98,7 +99,7 @@ export class Session {
|
|||
tick: update["tick"]
|
||||
}));
|
||||
}
|
||||
if (update["playing"]) {
|
||||
if (update.hasOwnProperty("playing")) {
|
||||
this.socket.send(JSON.stringify({
|
||||
type: 'play',
|
||||
session: this.sessionName,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue