这个网站上的鸟和鱼应该感觉活着,在标题和页脚里的天空和海岸线条纹上漂流。什么我没有想的是直到一个读者(我,像一个正常人一样测试我自己的网站一次)指出什么发生时你碰撞刷新。每次,整个场景重置。不同的鸟,不同的起始位置,像天空得到擦了并重新填充来自零时刻你看起来走。它没有感觉活着,它感觉随机重新生成,这是一个不同的东西全部。
要求,在我自己的字里
我写自己一个笔记在那时那说,更多或更少,"随机如果在五分钟内访问...如果不追踪你在哪里留下和坚持如果改变页面。"点击围绕网站应该不接触场景所有,自从组件从根布局挂载一次并保留安装整个时间你在导航客户端。那部分已经是自由的,SvelteKit不重新安装一个组件仅仅因为URL改变在它下面。实际的间隙是另一个情况:一个硬重新加载,或打开一个新标签。那是时刻组件从无重新安装,而且直到那点,"从无"意思一个全新的随机抽取每次。
第一个版本不是错的,它是冻结的
我在修复这的第一个尝试是几乎对而完全打破用我没有注意到一天方式。我保存了画的场景到localStorage,那哪个精灵得到选中和那点在他们的飞行周期每一个在,而且恢复它在下一个安装而不是重新角色:
// 第一个尝试:保存阶段,恢复完全相同的阶段
function restoreScene(saved: ScenePhases): ScenePhases {
return saved;
}那部分工作,在保存的数据来回完好的意义。什么我忘了是时间。我保存了完全的阶段每个鸟在当页面卸载,而且在下一个加载我只是...把他们放在那里完全相同。冻结。如果你让标签打开在你的坞站两分钟而且回来,鸟还没有移动一寸,因为无东西是会计处理两分钟那实际上通过了在真实的世界。
真实的修复需要推进每个保存的阶段向前用访客实际上多么长远的距离,正确地包装每个鸟的自己的周期长所以一个鸟那飞边到边每75秒而且已经被"离开"200秒来回一部分通过它的第三圈,不坚持在二十二秒的圈子一永远:
export function advancePhases(
phases: ScenePhases,
cycles: Record<string, number>,
elapsedMs: number
): ScenePhases {
const elapsedS = Math.max(0, elapsedMs) / 1000;
const advanced: ScenePhases = {};
for (const [key, value] of Object.entries(phases)) {
const cycle = cycles[key];
if (!cycle || cycle <= 0 || !Number.isFinite(value)) {
advanced[key] = value;
continue;
}
advanced[key] = (((value + elapsedS) % cycle) + cycle) % cycle;
}
return advanced;
}双模数在末尾((((value + elapsedS) % cycle) + cycle) % cycle)在这里因为JavaScript的%可以返回一个负的结果如果左边是负,而且我想要这个函数安全的调用用一个奇怪的或负的经过时间也(时钟偏斜,一个保存的时间戳从未来,无论什么)没有生产一个阶段在它的自己有效范围外。皮带和吊带,但廉价的。
陈旧性也有一个盖
无的这应该永远坚持。一个场景从六小时前不是"同样的访问继续,"它是一个新的访问那碰巧分享一个浏览器。所以保存的场景进位它的自己年龄检查而且在五分钟后期望:
export const SCENE_MAX_AGE_MS = 5 * 60 * 1000;通过那,或如果无保存,或如果保存的数据不解析,它只是角色一个新鲜的场景像它总是以前在无这个存在的。我确保了每读这里包装在尝试/捕捉也,Safari在私人浏览扔仅仅接触localStorage根本,而且一个访客用储存禁用不应该得到一个打破页面,他们应该只得到有普通新鲜角色场景物没有记忆附加它。失去坚持安静是好。扔一个错误其中一个鸟应该已经是不。
折皱:什么实际上数作为"重新加载"
这里是部分那花我最长的得对,在我已经想我是完成之后。一个访客谁故意重新加载页面,打完美的刷新按钮因为他们想看页面新鲜,期望一个重新加载打乱东西,同样的作为首次访问会。但我的恢复逻辑不能区别"你打刷新"从"你点击了一个链接"从内部组件,因为通过时间它运行,所有它知道是"一个安装发生。"两个情况只是看起来像一个安装。
浏览器实际上告诉你区别,通过一个API我不知道存在直到我去查阅:performance.getEntriesByType('navigation')返回一个入口谁的type是字面上'reload'对于一个故意的重新加载,对抗'navigate'对于基本上所有东西别的:
export function safeNavigationType(): NavigationType | undefined {
try {
if (typeof performance === 'undefined' || typeof performance.getEntriesByType !== 'function') {
return undefined;
}
const [entry] = performance.getEntriesByType('navigation') as PerformanceNavigationTiming[];
return entry?.type;
} catch {
return undefined;
}
}所有这个数学,角色,保存,恢复,阶段推进,住在一个平JavaScript模块完全分离从Svelte组件那呈现实际的鸟,特别所以我可能写实单元测试反对它没有需要一个浏览器或一个Svelte运行时旋转。给定一个假在-记忆存储对象而且一个固定时间戳代替真实Date.now(),我能资产完全什么一个五分钟旧的保存恢复到,什么一个六分钟旧的一个跌倒回到,而且什么一个损坏的或部分写的保存做,全部没有接触一个实际的页面。那分离是什么做我充分的确信来发送经过时间修复在首位,我能看一个阶段数字着陆完全其中数学说他们应该,而不是眼睛-球一个鸟是否在一个真实浏览器标签中等待几分钟后"看起来大约正确"。
而且然后实际的决定,这读几乎无聊简单一旦两个片段上面存在支持它:
const saved = navigationType === 'reload' ? null : loadScene<unknown>(storage, SKY_COLONY_STORAGE_KEY, now);一个真实重新加载跳过保存的场景完全而且角色新鲜,故意,每次。任何东西别的,回退按钮,一个新鲜标签,只是围绕导航,恢复而且推进像正常。它是一个小区别而且它花费我更长的追踪向下比坚持逻辑本身做,但它是区别在场景表现的方式一个实际访客期望它和技术正确但烦恼。