diff --git a/i18n/template-ssr.pot b/i18n/template-ssr.pot index ba1849a..3ee9165 100644 --- a/i18n/template-ssr.pot +++ b/i18n/template-ssr.pot @@ -219,6 +219,30 @@ msgstr "" msgid "You are not banned" msgstr "" +#: src/routes/api/auth/logout.js:11 +msgid "You are not even logged in." +msgstr "" + +#: src/routes/api/auth/delete_account.js:55 +#: src/routes/api/auth/logout.js:20 +msgid "Server error when logging out." +msgstr "" + +#: src/routes/api/auth/verify.js:26 +#: src/routes/api/auth/verify.js:35 +msgid "Mail verification" +msgstr "" + +#: src/routes/api/auth/verify.js:27 +msgid "You are now verified :)" +msgstr "" + +#: src/routes/api/auth/verify.js:35 +msgid "" +"Your mail verification code is invalid or already expired :(, please " +"request a new one." +msgstr "" + #: src/routes/api/auth/change_mail.js:21 #: src/routes/api/auth/register.js:24 msgid "This email provider is not allowed" @@ -256,15 +280,6 @@ msgstr "" msgid "Failed to establish session after register :(" msgstr "" -#: src/routes/api/auth/logout.js:11 -msgid "You are not even logged in." -msgstr "" - -#: src/routes/api/auth/delete_account.js:55 -#: src/routes/api/auth/logout.js:20 -msgid "Server error when logging out." -msgstr "" - #: src/routes/api/auth/change_mail.js:43 #: src/routes/api/auth/change_passwd.js:34 #: src/routes/api/auth/delete_account.js:35 @@ -277,21 +292,6 @@ msgstr "" msgid "Incorrect password!" msgstr "" -#: src/routes/api/auth/verify.js:26 -#: src/routes/api/auth/verify.js:35 -msgid "Mail verification" -msgstr "" - -#: src/routes/api/auth/verify.js:27 -msgid "You are now verified :)" -msgstr "" - -#: src/routes/api/auth/verify.js:35 -msgid "" -"Your mail verification code is invalid or already expired :(, please " -"request a new one." -msgstr "" - #: src/ssr/RedirectionPage.jsx:19 msgid "PixelPlanet.fun Accounts" msgstr "" diff --git a/i18n/template.pot b/i18n/template.pot index 95de2e3..5f80a08 100644 --- a/i18n/template.pot +++ b/i18n/template.pot @@ -46,136 +46,136 @@ msgstr "" msgid "Hide Hidden Canvases" msgstr "" -#: src/ui/PixelTransferController.js:63 +#: src/ui/PixelTransferController.js:70 msgid "Error :(" msgstr "" -#: src/ui/PixelTransferController.js:64 +#: src/ui/PixelTransferController.js:71 msgid "Didn't get an answer from pixelplanet. Maybe try to refresh?" msgstr "" -#: src/ui/PixelTransferController.js:96 +#: src/ui/PixelTransferController.js:103 msgid "Invalid Canvas" msgstr "" -#: src/ui/PixelTransferController.js:97 +#: src/ui/PixelTransferController.js:104 msgid "This canvas doesn't exist" msgstr "" -#: src/ui/PixelTransferController.js:100 -#: src/ui/PixelTransferController.js:104 -#: src/ui/PixelTransferController.js:108 +#: src/ui/PixelTransferController.js:107 +#: src/ui/PixelTransferController.js:111 +#: src/ui/PixelTransferController.js:115 msgid "Invalid Coordinates" msgstr "" -#: src/ui/PixelTransferController.js:101 +#: src/ui/PixelTransferController.js:108 msgid "x out of bounds" msgstr "" -#: src/ui/PixelTransferController.js:105 +#: src/ui/PixelTransferController.js:112 msgid "y out of bounds" msgstr "" -#: src/ui/PixelTransferController.js:109 +#: src/ui/PixelTransferController.js:116 msgid "z out of bounds" msgstr "" -#: src/ui/PixelTransferController.js:112 +#: src/ui/PixelTransferController.js:119 msgid "Wrong Color" msgstr "" -#: src/ui/PixelTransferController.js:113 +#: src/ui/PixelTransferController.js:120 msgid "Invalid color selected" msgstr "" -#: src/ui/PixelTransferController.js:116 +#: src/ui/PixelTransferController.js:123 msgid "Just for registered Users" msgstr "" -#: src/ui/PixelTransferController.js:117 +#: src/ui/PixelTransferController.js:124 msgid "You have to be logged in to place on this canvas" msgstr "" -#: src/ui/PixelTransferController.js:120 +#: src/ui/PixelTransferController.js:127 msgid "Place more :)" msgstr "" -#: src/ui/PixelTransferController.js:122 +#: src/ui/PixelTransferController.js:129 msgid "You can not access this canvas yet. You need to place more pixels" msgstr "" -#: src/ui/PixelTransferController.js:125 +#: src/ui/PixelTransferController.js:132 msgid "Pixel protected!" msgstr "" -#: src/ui/PixelTransferController.js:132 +#: src/ui/PixelTransferController.js:139 msgid "Please prove that you are human" msgstr "" -#: src/ui/PixelTransferController.js:136 +#: src/ui/PixelTransferController.js:143 msgid "No Proxies Allowed :(" msgstr "" -#: src/ui/PixelTransferController.js:137 +#: src/ui/PixelTransferController.js:144 msgid "You are using a Proxy." msgstr "" -#: src/ui/PixelTransferController.js:140 +#: src/ui/PixelTransferController.js:147 msgid "Not allowed" msgstr "" -#: src/ui/PixelTransferController.js:141 +#: src/ui/PixelTransferController.js:148 msgid "Just the Top10 of yesterday can place here" msgstr "" -#: src/ui/PixelTransferController.js:144 +#: src/ui/PixelTransferController.js:151 msgid "You are weird" msgstr "" -#: src/ui/PixelTransferController.js:146 +#: src/ui/PixelTransferController.js:153 msgid "Server got confused by your pixels. Are you playing on multiple devices?" msgstr "" -#: src/ui/PixelTransferController.js:149 +#: src/ui/PixelTransferController.js:156 msgid "Banned" msgstr "" -#: src/ui/PixelTransferController.js:153 +#: src/ui/PixelTransferController.js:160 msgid "Range Banned" msgstr "" -#: src/ui/PixelTransferController.js:154 +#: src/ui/PixelTransferController.js:161 msgid "Your Internet Provider is banned from playing this game" msgstr "" -#: src/ui/PixelTransferController.js:157 +#: src/ui/PixelTransferController.js:164 msgid "Timeout" msgstr "" -#: src/ui/PixelTransferController.js:159 +#: src/ui/PixelTransferController.js:166 msgid "" "Didn't get an answer from pixelplanet. Maybe try to refresh if problem " "persists?" msgstr "" -#: src/ui/PixelTransferController.js:162 +#: src/ui/PixelTransferController.js:169 msgid "Weird" msgstr "" -#: src/ui/PixelTransferController.js:163 +#: src/ui/PixelTransferController.js:170 msgid "Couldn't set Pixel" msgstr "" -#: src/ui/PixelTransferController.js:168 +#: src/ui/PixelTransferController.js:175 #, javascript-format msgid "Error ${ retCode }" msgstr "" -#: src/ui/renderer.js:35 +#: src/ui/rendererFactory.js:30 msgid "Canvas Error" msgstr "" -#: src/ui/renderer.js:36 +#: src/ui/rendererFactory.js:31 msgid "Can't render 3D canvas, do you have WebGL2 disabled?" msgstr "" @@ -248,6 +248,11 @@ msgstr "" msgid "Pixels placed" msgstr "" +#: src/components/buttons/CanvasSwitchButton.jsx:20 +#: src/components/windows/index.js:19 +msgid "Canvas Selection" +msgstr "" + #: src/components/buttons/ChatButton.jsx:88 msgid "Close Chat" msgstr "" @@ -256,11 +261,6 @@ msgstr "" msgid "Open Chat" msgstr "" -#: src/components/buttons/CanvasSwitchButton.jsx:20 -#: src/components/windows/index.js:19 -msgid "Canvas Selection" -msgstr "" - #: src/components/buttons/ExpandMenuButton.jsx:22 msgid "Close Menu" msgstr "" @@ -310,13 +310,8 @@ msgstr "" msgid "Resize" msgstr "" -#: src/components/buttons/DownloadButton.jsx:36 -msgid "Make Screenshot" -msgstr "" - -#: src/components/buttons/LogInButton.jsx:20 -#: src/components/windows/index.js:15 -msgid "User Area" +#: src/components/buttons/GlobeButton.jsx:34 +msgid "Globe View" msgstr "" #: src/components/buttons/PalselButton.jsx:30 @@ -327,8 +322,9 @@ msgstr "" msgid "Open Palette" msgstr "" -#: src/components/buttons/GlobeButton.jsx:34 -msgid "Globe View" +#: src/components/buttons/SettingsButton.jsx:21 +#: src/components/windows/index.js:14 +msgid "Settings" msgstr "" #: src/components/BanInfo.jsx:75 @@ -337,9 +333,13 @@ msgstr "" msgid "Help" msgstr "" -#: src/components/buttons/SettingsButton.jsx:21 -#: src/components/windows/index.js:14 -msgid "Settings" +#: src/components/buttons/LogInButton.jsx:20 +#: src/components/windows/index.js:15 +msgid "User Area" +msgstr "" + +#: src/components/buttons/DownloadButton.jsx:36 +msgid "Make Screenshot" msgstr "" #: src/components/windows/index.js:16 @@ -447,6 +447,99 @@ msgstr "" msgid "Why?" msgstr "" +#: src/components/windows/Settings.jsx:86 +msgid "Show Grid" +msgstr "" + +#: src/components/windows/Settings.jsx:91 +msgid "Turn on grid to highlight pixel borders." +msgstr "" + +#: src/components/windows/Settings.jsx:94 +msgid "Show Pixel Activity" +msgstr "" + +#: src/components/windows/Settings.jsx:99 +msgid "Show circles where pixels are placed." +msgstr "" + +#: src/components/windows/Settings.jsx:102 +msgid "Disable Game Sounds" +msgstr "" + +#: src/components/windows/Settings.jsx:108 +msgid "All sound effects will be disabled." +msgstr "" + +#: src/components/windows/Settings.jsx:112 +msgid "" +"Your Browser doesn't allow us to use AudioContext to play sounds. Do you " +"have some privacy feature blocking us?" +msgstr "" + +#: src/components/windows/Settings.jsx:118 +msgid "Enable chat notifications" +msgstr "" + +#: src/components/windows/Settings.jsx:122 +msgid "Play a sound when new chat messages arrive" +msgstr "" + +#: src/components/windows/Settings.jsx:125 +msgid "Auto Zoom In" +msgstr "" + +#: src/components/windows/Settings.jsx:130 +msgid "" +"Zoom in instead of placing a pixel when you tap the canvas and your zoom is " +"small." +msgstr "" + +#: src/components/windows/Settings.jsx:133 +msgid "Compact Palette" +msgstr "" + +#: src/components/windows/Settings.jsx:138 +msgid "Display Palette in a compact form that takes less screen space." +msgstr "" + +#: src/components/windows/Settings.jsx:141 +msgid "Potato Mode" +msgstr "" + +#: src/components/windows/Settings.jsx:145 +msgid "For when you are playing on a potato." +msgstr "" + +#: src/components/Converter.jsx:376 +#: src/components/windows/Settings.jsx:148 +msgid "Light Grid" +msgstr "" + +#: src/components/windows/Settings.jsx:152 +msgid "Show Grid in white instead of black." +msgstr "" + +#: src/components/windows/Settings.jsx:156 +msgid "Historical View" +msgstr "" + +#: src/components/windows/Settings.jsx:161 +msgid "Check out past versions of the canvas." +msgstr "" + +#: src/components/windows/Settings.jsx:166 +msgid "Themes" +msgstr "" + +#: src/components/windows/Settings.jsx:171 +msgid "How pixelplanet should look like." +msgstr "" + +#: src/components/windows/Settings.jsx:178 +msgid "Select Language" +msgstr "" + #: src/components/windows/Help.jsx:42 msgid "Place color pixels on a large canvas with other players online!" msgstr "" @@ -689,99 +782,6 @@ msgstr "" msgid "Submit" msgstr "" -#: src/components/windows/Settings.jsx:86 -msgid "Show Grid" -msgstr "" - -#: src/components/windows/Settings.jsx:91 -msgid "Turn on grid to highlight pixel borders." -msgstr "" - -#: src/components/windows/Settings.jsx:94 -msgid "Show Pixel Activity" -msgstr "" - -#: src/components/windows/Settings.jsx:99 -msgid "Show circles where pixels are placed." -msgstr "" - -#: src/components/windows/Settings.jsx:102 -msgid "Disable Game Sounds" -msgstr "" - -#: src/components/windows/Settings.jsx:108 -msgid "All sound effects will be disabled." -msgstr "" - -#: src/components/windows/Settings.jsx:112 -msgid "" -"Your Browser doesn't allow us to use AudioContext to play sounds. Do you " -"have some privacy feature blocking us?" -msgstr "" - -#: src/components/windows/Settings.jsx:118 -msgid "Enable chat notifications" -msgstr "" - -#: src/components/windows/Settings.jsx:122 -msgid "Play a sound when new chat messages arrive" -msgstr "" - -#: src/components/windows/Settings.jsx:125 -msgid "Auto Zoom In" -msgstr "" - -#: src/components/windows/Settings.jsx:130 -msgid "" -"Zoom in instead of placing a pixel when you tap the canvas and your zoom is " -"small." -msgstr "" - -#: src/components/windows/Settings.jsx:133 -msgid "Compact Palette" -msgstr "" - -#: src/components/windows/Settings.jsx:138 -msgid "Display Palette in a compact form that takes less screen space." -msgstr "" - -#: src/components/windows/Settings.jsx:141 -msgid "Potato Mode" -msgstr "" - -#: src/components/windows/Settings.jsx:145 -msgid "For when you are playing on a potato." -msgstr "" - -#: src/components/Converter.jsx:376 -#: src/components/windows/Settings.jsx:148 -msgid "Light Grid" -msgstr "" - -#: src/components/windows/Settings.jsx:152 -msgid "Show Grid in white instead of black." -msgstr "" - -#: src/components/windows/Settings.jsx:156 -msgid "Historical View" -msgstr "" - -#: src/components/windows/Settings.jsx:161 -msgid "Check out past versions of the canvas." -msgstr "" - -#: src/components/windows/Settings.jsx:166 -msgid "Themes" -msgstr "" - -#: src/components/windows/Settings.jsx:171 -msgid "How pixelplanet should look like." -msgstr "" - -#: src/components/windows/Settings.jsx:178 -msgid "Select Language" -msgstr "" - #: src/components/windows/CanvasSelect.jsx:29 msgid "" "Select the canvas you want to use. Every canvas is unique and has " @@ -826,6 +826,14 @@ msgid "" "how the canvas was at that time." msgstr "" +#: src/components/windows/ForgotPassword.jsx:58 +msgid "Sent you a mail with instructions to reset your password." +msgstr "" + +#: src/components/windows/ForgotPassword.jsx:69 +msgid "Enter your mail address and we will send you a new password:" +msgstr "" + #: src/components/windows/Chat.jsx:180 msgid "Start chatting here" msgstr "" @@ -842,14 +850,6 @@ msgstr "" msgid "Channel settings" msgstr "" -#: src/components/windows/ForgotPassword.jsx:58 -msgid "Sent you a mail with instructions to reset your password." -msgstr "" - -#: src/components/windows/ForgotPassword.jsx:69 -msgid "Enter your mail address and we will send you a new password:" -msgstr "" - #: src/components/Captcha.jsx:51 #: src/components/Captcha.jsx:105 msgid "Could not load captcha" @@ -931,6 +931,13 @@ msgstr "" msgid "Password must be shorter than 60 characters." msgstr "" +#: src/components/ChangeMail.jsx:91 +#: src/components/ChangeName.jsx:68 +#: src/components/ChangePassword.jsx:109 +#: src/components/LanguageSelect.jsx:80 +msgid "Save" +msgstr "" + #: src/components/GetIID.jsx:44 msgid "Get IID" msgstr "" @@ -1121,13 +1128,6 @@ msgstr "" msgid "Ranking updates every 5 min. Daily rankings get reset at midnight UTC." msgstr "" -#: src/components/ChangeMail.jsx:91 -#: src/components/ChangeName.jsx:68 -#: src/components/ChangePassword.jsx:109 -#: src/components/LanguageSelect.jsx:80 -msgid "Save" -msgstr "" - #: src/components/CanvasItem.jsx:29 msgid "Online Users" msgstr "" @@ -1201,6 +1201,24 @@ msgstr "" msgid "LogIn" msgstr "" +#: src/components/UserMessages.jsx:28 +msgid "" +"Please verify your mail address or your account could get deleted after a " +"few days." +msgstr "" + +#: src/components/UserMessages.jsx:49 +msgid "A new verification mail is getting sent to you." +msgstr "" + +#: src/components/UserMessages.jsx:53 +msgid "Click here to request a new verification mail." +msgstr "" + +#: src/components/ChangeName.jsx:64 +msgid "New Username" +msgstr "" + #: src/components/ChangePassword.jsx:21 msgid "Passwords do not match." msgstr "" @@ -1221,20 +1239,6 @@ msgstr "" msgid "Confirm New Password" msgstr "" -#: src/components/UserMessages.jsx:28 -msgid "" -"Please verify your mail address or your account could get deleted after a " -"few days." -msgstr "" - -#: src/components/UserMessages.jsx:49 -msgid "A new verification mail is getting sent to you." -msgstr "" - -#: src/components/UserMessages.jsx:53 -msgid "Click here to request a new verification mail." -msgstr "" - #: src/components/ChangeMail.jsx:59 msgid "" "Changed Mail successfully. We sent you a verification mail, " @@ -1245,14 +1249,34 @@ msgstr "" msgid "New Mail" msgstr "" -#: src/components/ChangeName.jsx:64 -msgid "New Username" -msgstr "" - #: src/components/DeleteAccount.jsx:66 msgid "Yes, Delete My Account!" msgstr "" +#: src/components/SocialSettings.jsx:35 +msgid "Block DMs" +msgstr "" + +#: src/components/SocialSettings.jsx:42 +msgid "Block all Private Messages" +msgstr "" + +#: src/components/SocialSettings.jsx:44 +msgid "Private" +msgstr "" + +#: src/components/SocialSettings.jsx:51 +msgid "Don't show me in global stats" +msgstr "" + +#: src/components/SocialSettings.jsx:57 +msgid "Unblock Users" +msgstr "" + +#: src/components/SocialSettings.jsx:82 +msgid "You have no users blocked" +msgstr "" + #: src/components/ModCanvastools.jsx:168 msgid "Build image on canvas." msgstr "" @@ -1346,6 +1370,54 @@ msgstr "" msgid "Stop Cleaner" msgstr "" +#: src/components/ModWatchtools.jsx:48 +msgid "Interval is invalid" +msgstr "" + +#: src/components/ModWatchtools.jsx:122 +msgid "Check who placed in an area" +msgstr "" + +#: src/components/ModWatchtools.jsx:123 +msgid "Canvas" +msgstr "" + +#: src/components/ModWatchtools.jsx:142 +msgid "Interval" +msgstr "" + +#: src/components/ModWatchtools.jsx:157 +msgid "IID (optional)" +msgstr "" + +#: src/components/ModWatchtools.jsx:236 +msgid "Get Pixels" +msgstr "" + +#: src/components/ModWatchtools.jsx:267 +msgid "Get Users" +msgstr "" + +#: src/components/ModIIDtools.jsx:20 +msgid "You must enter a duration" +msgstr "" + +#: src/components/ModIIDtools.jsx:24 +msgid "You must enter an IID" +msgstr "" + +#: src/components/ModIIDtools.jsx:53 +msgid "IID Actions" +msgstr "" + +#: src/components/ModIIDtools.jsx:80 +msgid "Enter Reason" +msgstr "" + +#: src/components/ModIIDtools.jsx:97 +msgid "(0 = infinite)" +msgstr "" + #: src/components/Admintools.jsx:109 msgid "IP Actions" msgstr "" @@ -1378,76 +1450,8 @@ msgstr "" msgid "User Name" msgstr "" -#: src/components/SocialSettings.jsx:35 -msgid "Block DMs" -msgstr "" - -#: src/components/SocialSettings.jsx:42 -msgid "Block all Private Messages" -msgstr "" - -#: src/components/SocialSettings.jsx:44 -msgid "Private" -msgstr "" - -#: src/components/SocialSettings.jsx:51 -msgid "Don't show me in global stats" -msgstr "" - -#: src/components/SocialSettings.jsx:57 -msgid "Unblock Users" -msgstr "" - -#: src/components/SocialSettings.jsx:82 -msgid "You have no users blocked" -msgstr "" - -#: src/components/ModIIDtools.jsx:20 -msgid "You must enter a duration" -msgstr "" - -#: src/components/ModIIDtools.jsx:24 -msgid "You must enter an IID" -msgstr "" - -#: src/components/ModIIDtools.jsx:53 -msgid "IID Actions" -msgstr "" - -#: src/components/ModIIDtools.jsx:80 -msgid "Enter Reason" -msgstr "" - -#: src/components/ModIIDtools.jsx:97 -msgid "(0 = infinite)" -msgstr "" - -#: src/components/ModWatchtools.jsx:48 -msgid "Interval is invalid" -msgstr "" - -#: src/components/ModWatchtools.jsx:122 -msgid "Check who placed in an area" -msgstr "" - -#: src/components/ModWatchtools.jsx:123 -msgid "Canvas" -msgstr "" - -#: src/components/ModWatchtools.jsx:142 -msgid "Interval" -msgstr "" - -#: src/components/ModWatchtools.jsx:157 -msgid "IID (optional)" -msgstr "" - -#: src/components/ModWatchtools.jsx:236 -msgid "Get Pixels" -msgstr "" - -#: src/components/ModWatchtools.jsx:267 -msgid "Get Users" +#: src/components/contextmenus/ChannelContextMenu.jsx:46 +msgid "Mute" msgstr "" #: src/components/contextmenus/UserContextMenu.jsx:49 @@ -1462,10 +1466,6 @@ msgstr "" msgid "Block" msgstr "" -#: src/components/contextmenus/ChannelContextMenu.jsx:46 -msgid "Mute" -msgstr "" - #: src/components/windows/Help.jsx:15 #: src/components/windows/Settings.jsx:87 msgctxt "keybinds" @@ -1478,6 +1478,11 @@ msgctxt "keybinds" msgid "X" msgstr "" +#: src/components/windows/Settings.jsx:103 +msgctxt "keybinds" +msgid "M" +msgstr "" + #: src/components/windows/Help.jsx:17 #: src/components/windows/Settings.jsx:158 msgctxt "keybinds" @@ -1527,9 +1532,4 @@ msgstr "" #: src/components/windows/Help.jsx:32 msgctxt "keybinds" msgid "C" -msgstr "" - -#: src/components/windows/Settings.jsx:103 -msgctxt "keybinds" -msgid "M" msgstr "" \ No newline at end of file diff --git a/src/client.js b/src/client.js index ac2dc92..6e5ac22 100644 --- a/src/client.js +++ b/src/client.js @@ -17,8 +17,9 @@ import { import pixelTransferController from './ui/PixelTransferController'; import store from './store/store'; import renderApp from './components/App'; -import { initRenderer, getRenderer } from './ui/renderer'; +import { initRenderer, getRenderer } from './ui/rendererFactory'; import socketClient from './socket/SocketClient'; +import { GC_INTERVAL } from './core/constants'; persistStore(store, {}, () => { window.addEventListener('message', store.dispatch); @@ -50,7 +51,7 @@ persistStore(store, {}, () => { store.dispatch(fetchMe()); - socketClient.initialize(store); + socketClient.initialize(store, pixelTransferController, getRenderer); }); (function load() { @@ -64,27 +65,8 @@ persistStore(store, {}, () => { // garbage collection setInterval(() => { const renderer = getRenderer(); - const chunks = renderer.getAllChunks(); - if (chunks) { - const curTime = Date.now(); - let cnt = 0; - chunks.forEach((value, key) => { - if (curTime > value.timestamp + 300000) { - const [zc, xc, yc] = value.cell; - if (!renderer.isChunkInView(zc, xc, yc)) { - cnt++; - if (value.isBasechunk) { - socketClient.deRegisterChunk([xc, yc]); - } - chunks.delete(key); - value.destructor(); - } - } - }); - // eslint-disable-next-line no-console - console.log('Garbage collection cleaned', cnt, 'chunks'); - } - }, 300000); + renderer.gc(); + }, GC_INTERVAL); document.removeEventListener('DOMContentLoaded', onLoad); }; diff --git a/src/components/Mobile3DControls.jsx b/src/components/Mobile3DControls.jsx index d1e1dc7..be8c06e 100644 --- a/src/components/Mobile3DControls.jsx +++ b/src/components/Mobile3DControls.jsx @@ -5,7 +5,7 @@ import React from 'react'; -import { getRenderer } from '../ui/renderer'; +import { getRenderer } from '../ui/rendererFactory'; const btnStyle = { fontSize: 34, diff --git a/src/components/buttons/DownloadButton.jsx b/src/components/buttons/DownloadButton.jsx index baadb20..bcdfb7d 100644 --- a/src/components/buttons/DownloadButton.jsx +++ b/src/components/buttons/DownloadButton.jsx @@ -8,7 +8,7 @@ import { MdFileDownload } from 'react-icons/md'; import fileDownload from 'js-file-download'; import { t } from 'ttag'; -import { getRenderer } from '../../ui/renderer'; +import { getRenderer } from '../../ui/rendererFactory'; /** * https://jsfiddle.net/AbdiasSoftware/7PRNN/ diff --git a/src/core/constants.js b/src/core/constants.js index ad47325..d5dbba6 100644 --- a/src/core/constants.js +++ b/src/core/constants.js @@ -96,3 +96,8 @@ export const MAX_CHAT_MESSAGES = 100; export const EVENT_USER_NAME = 'event'; export const INFO_USER_NAME = 'info'; export const APISOCKET_USER_NAME = 'apisocket'; + +// maximum chunks to subscribe to +export const MAX_LOADED_CHUNKS = 2000; +export const MAX_CHUNK_AGE = 300000; +export const GC_INTERVAL = 300000; diff --git a/src/socket/SocketClient.js b/src/socket/SocketClient.js index 8dd056b..a1c21d8 100644 --- a/src/socket/SocketClient.js +++ b/src/socket/SocketClient.js @@ -10,7 +10,7 @@ import { dehydrateRegCanvas, dehydrateRegChunk, dehydrateRegMChunks, - dehydrateDeRegChunk, + dehydrateDeRegMChunks, dehydratePixelUpdate, dehydratePing, } from './packets/client'; @@ -35,15 +35,15 @@ import { fetchMe, } from '../store/actions/thunks'; import { shardHost } from '../store/actions/fetch'; -import pixelTransferController from '../ui/PixelTransferController'; - -const chunks = []; class SocketClient { + store = null; + pixelTransferController = null; + ws = null; + getRenderer; + constructor() { console.log('Creating WebSocketClient'); - this.store = null; - this.ws = null; this.channelId = 0; /* * properties set in connect and open: @@ -59,12 +59,18 @@ class SocketClient { setInterval(this.checkHealth, 2000); } - initialize(store) { + initialize(store, pixelTransferController, getRenderer) { this.store = store; + if (pixelTransferController) { + this.pixelTransferController = pixelTransferController; + } + if (getRenderer) { + this.getRenderer = getRenderer; + } return this.connect(); } - async connect() { + connect() { this.readyState = WebSocket.CONNECTING; if (this.ws) { console.log('WebSocket already open, not starting'); @@ -139,8 +145,13 @@ class SocketClient { this.send(dehydrateRegCanvas( this.store.getState().canvas.canvasId, )); - console.log(`Register ${chunks.length} chunks`); - this.send(dehydrateRegMChunks(chunks)); + // register chunks + const chunkids = this.getRenderer().recChunkIds; + if (chunkids.length) { + console.log(`Register ${chunkids.length} chunks`); + this.send(dehydrateRegMChunks(chunkids)); + } + // flush queue this.processMsgQueue(); } @@ -151,29 +162,21 @@ class SocketClient { console.log( `Notify websocket server that we changed canvas to ${canvasId}`, ); - chunks.length = 0; this.send(dehydrateRegCanvas(canvasId)); } - registerChunk(cell) { - const [i, j] = cell; - const chunkid = (i << 8) | j; - chunks.push(chunkid); + registerChunk(chunkid) { const buffer = dehydrateRegChunk(chunkid); if (this.readyState === WebSocket.OPEN) { this.send(buffer); } } - deRegisterChunk(cell) { - const [i, j] = cell; - const chunkid = (i << 8) | j; - const buffer = dehydrateDeRegChunk(chunkid); + deRegisterChunks(chunkids) { + const buffer = dehydrateDeRegMChunks(chunkids); if (this.readyState === WebSocket.OPEN) { this.send(buffer); } - const pos = chunks.indexOf(chunkid); - if (~pos) chunks.splice(pos, 1); } /* @@ -274,7 +277,11 @@ class SocketClient { switch (opcode) { case PIXEL_UPDATE_OP: - pixelTransferController.receivePixelUpdate(hydratePixelUpdate(data)); + if (this.pixelTransferController) { + this.pixelTransferController.receivePixelUpdate( + hydratePixelUpdate(data), + ); + } break; case PIXEL_RETURN_OP: { const pos = this.reqQueue.findIndex((q) => q[0] === 'pu'); diff --git a/src/store/actions/index.js b/src/store/actions/index.js index 0430588..b360526 100644 --- a/src/store/actions/index.js +++ b/src/store/actions/index.js @@ -218,30 +218,31 @@ export function requestBigChunk(center) { }; } -export function preLoadedBigChunk( - center, -) { +export function removeChunks(chunks) { + return { + type: 'REMOVE_CHUNKS', + chunks, + }; +} + +export function preLoadedBigChunk(center) { return { type: 'PRE_LOADED_BIG_CHUNK', center, }; } -export function receiveBigChunk( - center, - chunk, -) { +export function receiveBigChunk(chunk) { return { type: 'REC_BIG_CHUNK', - center, chunk, }; } -export function receiveBigChunkFailure(center, error) { +export function receiveBigChunkFailure(chunk, error) { return { type: 'REC_BIG_CHUNK_FAILURE', - center, + chunk, error, }; } diff --git a/src/store/middleware/rendererHook.js b/src/store/middleware/rendererHook.js index 72b85dd..0d26cbc 100644 --- a/src/store/middleware/rendererHook.js +++ b/src/store/middleware/rendererHook.js @@ -9,7 +9,7 @@ import { getRenderer, initRenderer, -} from '../../ui/renderer'; +} from '../../ui/rendererFactory'; export default (store) => (next) => (action) => { const { type } = action; diff --git a/src/store/middleware/socketClientHook.js b/src/store/middleware/socketClientHook.js index d58edfe..d37368e 100644 --- a/src/store/middleware/socketClientHook.js +++ b/src/store/middleware/socketClientHook.js @@ -9,11 +9,21 @@ export default (store) => (next) => (action) => { switch (action.type) { case 'REC_BIG_CHUNK': case 'REC_BIG_CHUNK_FAILURE': { - if (!action.center) { - break; + const { chunk } = action; + if (chunk.recUpdates) { + SocketClient.registerChunk(action.chunk.id); + } + break; + } + + case 'REMOVE_CHUNKS': { + const { chunks } = action; + const ids = chunks + .filter((chunk) => chunk.recUpdates) + .map((chunk) => chunk.id); + if (ids.length) { + SocketClient.deRegisterChunks(ids); } - const [, cx, cy] = action.center; - SocketClient.registerChunk([cx, cy]); break; } diff --git a/src/store/reducers/fetching.js b/src/store/reducers/fetching.js index 09d3852..195205d 100644 --- a/src/store/reducers/fetching.js +++ b/src/store/reducers/fetching.js @@ -42,15 +42,7 @@ export default function fetching( }; } - case 'REC_BIG_CHUNK': { - const { fetchingChunks } = state; - - return { - ...state, - fetchingChunks: fetchingChunks - 1, - }; - } - + case 'REC_BIG_CHUNK': case 'REC_BIG_CHUNK_FAILURE': { const { fetchingChunks } = state; diff --git a/src/ui/Chunk.js b/src/ui/Chunk.js new file mode 100644 index 0000000..4faacf9 --- /dev/null +++ b/src/ui/Chunk.js @@ -0,0 +1,37 @@ +/* + * parent class for Chunk + */ + +/* eslint-disable class-methods-use-this */ + +class Chunk { + // if chunk receives updates via websocket + recUpdates = false; + // timestamp of last touch, + // mustbe regularly updated for GC, + // either by touch() or by setting directly + timestamp; + // coordiantes + z; + i; + j; + + constructor(z, i, j) { + this.timestamp = Date.now(); + this.z = z; + this.i = i; + this.j = j; + } + + get id() { + return (this.i << 8) | this.j; + } + + touch() { + this.timestamp = Date.now(); + } + + destructor() {} +} + +export default Chunk; diff --git a/src/ui/ChunkRGB.js b/src/ui/Chunk2D.js similarity index 79% rename from src/ui/ChunkRGB.js rename to src/ui/Chunk2D.js index 0cc82dd..9481e77 100644 --- a/src/ui/ChunkRGB.js +++ b/src/ui/Chunk2D.js @@ -1,47 +1,30 @@ import { TILE_SIZE } from '../core/constants'; +import Chunk from './Chunk'; -class ChunkRGB { - // array of coords [zoom, cx, cy] - // just an identifier, could be removed - cell; +class Chunk2D extends Chunk { // HTMLCanvasElement of chunk image; // boolean if chunk loeaded (request done) ready; // boolean if chunk is empty isEmpty; - // number timestamp of last load for garbage collection - timestamp; // palette of canvas palette; - // boolean if it is basechunk, rather than zoomtile - isBasechunk; constructor(palette, zoom = 0, cx = 0, cy = 0) { - // isBasechunk gets set to true by REC_BIG_CHUNK - // if true => chunk got requested from api/chunk and - // receives websocket pixel updates - // if false => chunk is an zoomed png tile - this.isBasechunk = false; + super(zoom, cx, cy); this.palette = palette; this.image = document.createElement('canvas'); this.image.width = TILE_SIZE; this.image.height = TILE_SIZE; - this.cell = [zoom, cx, cy]; this.ready = false; this.isEmpty = false; - this.timestamp = Date.now(); - } - - // eslint-disable-next-line class-methods-use-this - destructor() { - return null; } // from Uint8Array fromBuffer(chunkBuffer) { - this.isBasechunk = true; + this.recUpdates = true; const imageData = new ImageData(TILE_SIZE, TILE_SIZE); const imageView = new Uint32Array(imageData.data.buffer); const { abgr } = this.palette; @@ -110,7 +93,7 @@ class ChunkRGB { } hasColorIn(cell, color) { - const index = ChunkRGB.getIndexFromCell(cell); + const index = Chunk2D.getIndexFromCell(cell); const ctx = this.image.getContext('2d'); const imageData = ctx.getImageData(0, 0, TILE_SIZE, TILE_SIZE); @@ -128,4 +111,4 @@ class ChunkRGB { } } -export default ChunkRGB; +export default Chunk2D; diff --git a/src/ui/ChunkRGB3D.js b/src/ui/Chunk3D.js similarity index 95% rename from src/ui/ChunkRGB3D.js rename to src/ui/Chunk3D.js index 7290f53..141756f 100644 --- a/src/ui/ChunkRGB3D.js +++ b/src/ui/Chunk3D.js @@ -8,6 +8,7 @@ import * as THREE from 'three'; +import Chunk from './Chunk'; import { THREE_TILE_SIZE, THREE_CANVAS_HEIGHT, @@ -73,8 +74,7 @@ const material = new THREE.MeshLambertMaterial({ }); -class Chunk { - cell; // Array +class Chunk3D extends Chunk { key; // string ready = false; palette; // Object @@ -83,13 +83,12 @@ class Chunk { faceCnt; // number lastPixel; // number heightMap; // Array - timestamp; // number constructor(palette, key, xc, zc) { - this.cell = [0, xc, zc]; + super(0, xc, zc); + this.recUpdates = true; this.key = key; this.palette = palette; - this.timestamp = Date.now(); } destructor() { @@ -106,7 +105,7 @@ class Chunk { if (y < 0) return 1; // z and y are swapped in api/pixel for compatibility // with 2D canvas - const offset = Chunk.getOffsetOfVoxel(x, y, z); + const offset = Chunk3D.getOffsetOfVoxel(x, y, z); return this.buffer[offset]; } @@ -139,7 +138,7 @@ class Chunk { } } console.log(`Created buffer with ${cnt} voxels`); - const [faceCnt, lastPixel, heightMap] = Chunk.calculateMetaData(this.buffer); + const [faceCnt, lastPixel, heightMap] = Chunk3D.calculateMetaData(this.buffer); this.faceCnt = faceCnt; this.lastPixel = lastPixel; this.heightMap = heightMap; @@ -196,7 +195,7 @@ class Chunk { if (heighestPixel > totalHeight) { // last total pixel totalHeight = heighestPixel; - lastPixel = Chunk.getOffsetOfVoxel(x, heighestPixel, z); + lastPixel = Chunk3D.getOffsetOfVoxel(x, heighestPixel, z); } } } @@ -233,7 +232,7 @@ class Chunk { } setVoxel(x, y, z, clr) { - const offset = Chunk.getOffsetOfVoxel(x, y, z); + const offset = Chunk3D.getOffsetOfVoxel(x, y, z); this.setVoxelByOffset(offset, clr); } @@ -247,7 +246,7 @@ class Chunk { chunkBuffer.set(chunkBufferInpt); } this.buffer = chunkBuffer; - const [faceCnt, lastPixel, heightMap] = Chunk.calculateMetaData( + const [faceCnt, lastPixel, heightMap] = Chunk3D.calculateMetaData( chunkBuffer, ); this.faceCnt = faceCnt; @@ -383,4 +382,4 @@ class Chunk { } } -export default Chunk; +export default Chunk3D; diff --git a/src/ui/ChunkLoader.js b/src/ui/ChunkLoader.js new file mode 100644 index 0000000..85c4531 --- /dev/null +++ b/src/ui/ChunkLoader.js @@ -0,0 +1,131 @@ +/* + * parent class for storing chunks + */ +import { + requestBigChunk, + receiveBigChunk, + receiveBigChunkFailure, + removeChunks, + // fetching of preLoad chunk triggers rerender already + // lets keep this out for now, until needed + // preLoadedBigChunk, +} from '../store/actions'; +import { + MAX_LOADED_CHUNKS, + MAX_CHUNK_AGE, +} from '../core/constants'; + +/* eslint-disable class-methods-use-this */ + +class ChunkLoader { + #store; + // Map of chunkId: chunkRGB + #chunks; + #canvasId; + + constructor(store, canvasId) { + this.#store = store; + this.#chunks = new Map(); + this.#canvasId = canvasId; + } + + get canvasId() { + return this.#canvasId; + } + + get recChunkIds() { + const ids = []; + this.#chunks.forEach((chunk) => { + if (chunk.recUpdates) { + ids.push(chunk.id); + } + }); + return ids; + } + + destructor() { + this.#chunks.forEach((chunk) => { + chunk.destructor(); + }); + this.#chunks = new Map(); + } + + cget(key) { + return this.#chunks.get(key); + } + + cset(key, chunk) { + /* + * chunks are not neccessarily fully loaded here, + * but they are in bcRecChunk + */ + this.#chunks.set(key, chunk); + } + + bcReqChunk(chunk) { + const { z, i, j } = chunk; + this.#store.dispatch(requestBigChunk([z, i, j])); + } + + bcRecChunk(chunk) { + this.#store.dispatch(receiveBigChunk(chunk)); + const { size } = this.#chunks; + if (size > MAX_LOADED_CHUNKS) { + // hysteresis of 10% + const amountToRem = size - Math.floor(MAX_LOADED_CHUNKS * 0.9); + const chunksByTs = Array.from(this.#chunks.entries()) + .sort((a, b) => a[1].timestamp - b[1].timestamp) + .slice(0, amountToRem); + chunksByTs.forEach(([key]) => { + this.#chunks.delete(key); + }); + const remChunks = chunksByTs.map((c) => c[1]); + this.bcRemoveChunks(remChunks); + // eslint-disable-next-line max-len, no-console + console.log(`Cleared ${remChunks.length} to cut amount of chunks to ${this.#chunks.size}`); + } + } + + /* + * 404 (aka empty chunks) also trigger this + */ + bcReqChunkFail(chunk, error) { + this.#store.dispatch(receiveBigChunkFailure(chunk, error.message)); + } + + /* + * @param chunks chunks[] + */ + bcRemoveChunks(chunks) { + this.#store.dispatch(removeChunks(chunks)); + } + + /* + * delete chunks that didn't get seen for + * more than 5min + */ + gc(renderer) { + const threshold = Date.now() - MAX_CHUNK_AGE; + const chunks = this.#chunks; + const remChunks = []; + chunks.forEach((chunk, key) => { + if (threshold > chunk.timestamp) { + const { + z, + i, + j, + } = chunk; + if (!renderer.isChunkInView(z, i, j)) { + remChunks.push(chunk); + chunks.delete(key); + chunk.destructor(); + } + } + }); + this.bcRemoveChunks(remChunks); + // eslint-disable-next-line no-console + console.log(`GC cleaned ${remChunks.length} chunks.`); + } +} + +export default ChunkLoader; diff --git a/src/ui/ChunkLoader2D.js b/src/ui/ChunkLoader2D.js index db79ef3..08e8abc 100644 --- a/src/ui/ChunkLoader2D.js +++ b/src/ui/ChunkLoader2D.js @@ -2,19 +2,14 @@ * Fetching and storing of 2D chunks */ -import ChunkRGB from './ChunkRGB'; +import ChunkLoader from './ChunkLoader'; +import Chunk from './Chunk2D'; import { TILE_SIZE, TILE_ZOOM_LEVEL } from '../core/constants'; import { shardOrigin } from '../store/actions/fetch'; import { loadingTiles, loadImage, } from './loadImage'; -import { - requestBigChunk, - receiveBigChunk, - receiveBigChunkFailure, - // preLoadedBigChunk, -} from '../store/actions'; import { getMaxTiledZoom, getCellInsideChunk, @@ -22,18 +17,14 @@ import { getHistoricalCanvasSize, } from '../core/utils'; -class ChunkLoader { - store = null; - canvasId; +class ChunkLoader2D extends ChunkLoader { canvasMaxTiledZoom; historicalMaxTiledZooms; palette; canvasSize; - chunks; constructor(store, canvasId, palette, canvasSize, historicalSizes) { - this.store = store; - this.canvasId = canvasId; + super(store, canvasId); this.palette = palette; this.canvasSize = canvasSize; this.canvasMaxTiledZoom = getMaxTiledZoom(canvasSize); @@ -46,12 +37,6 @@ class ChunkLoader { } else { this.historicalMaxTiledZooms = []; } - - this.chunks = new Map(); - } - - getAllChunks() { - return this.chunks; } getPixelUpdate( @@ -60,7 +45,7 @@ class ChunkLoader { offset, color, ) { - const chunk = this.chunks.get(`${this.canvasMaxTiledZoom}:${cx}:${cy}`); + const chunk = this.cget(`${this.canvasMaxTiledZoom}:${cx}:${cy}`); if (chunk) { const ix = offset % TILE_SIZE; const iy = Math.floor(offset / TILE_SIZE); @@ -75,7 +60,7 @@ class ChunkLoader { const { canvasSize } = this; const [cx, cy] = getChunkOfPixel(canvasSize, x, y); const key = `${this.canvasMaxTiledZoom}:${cx}:${cy}`; - const chunk = this.chunks.get(key); + const chunk = this.cget(key); if (!chunk) { return 0; } @@ -107,7 +92,7 @@ class ChunkLoader { if (historicalTime && historicalTime !== '0000') { // eslint-disable-next-line max-len const incrementialChunkKey = `${historicalDate}${historicalTime}:${cx}:${cy}`; - const incrementialChunk = this.chunks.get(incrementialChunkKey); + const incrementialChunk = this.cget(incrementialChunkKey); if (incrementialChunk) { const incrementialColor = incrementialChunk.getColorIndex(px, false); incrementialChunk.timestamp = curTime; @@ -118,7 +103,7 @@ class ChunkLoader { } const chunkKey = `${historicalDate}:${cx}:${cy}`; - const chunk = this.chunks.get(chunkKey); + const chunk = this.cget(chunkKey); if (!chunk) { return null; } @@ -157,9 +142,6 @@ class ChunkLoader { const pcX = (cx % zoomDiffAbs) * TILE_SIZE / zoomDiffAbs; const pcY = (cy % zoomDiffAbs) * TILE_SIZE / zoomDiffAbs; chunkRGB.preLoad(plChunk, zoomDiffAbs, pcX, pcY); - // fetching of preLoad chunk triggers rerender already - // lets keep this commented out for now - // this.store.dispatch(preLoadedBigChunk([zoom, cx, cy])); return chunkRGB.image; } } catch (error) { @@ -181,17 +163,17 @@ class ChunkLoader { chunkPreLoading = true, ) { const chunkKey = `${zoom}:${cx}:${cy}`; - let chunkRGB = this.chunks.get(chunkKey); + let chunkRGB = this.cget(chunkKey); if (chunkRGB) { if (chunkRGB.ready) { return chunkRGB.image; } } else if (fetch) { - chunkRGB = new ChunkRGB(this.palette, zoom, cx, cy); - this.chunks.set(chunkKey, chunkRGB); + chunkRGB = new Chunk(this.palette, zoom, cx, cy); + this.cset(chunkKey, chunkRGB); // fetch chunk if (this.canvasMaxTiledZoom === zoom) { - this.fetchBaseChunk(zoom, cx, cy, chunkRGB); + this.fetchBaseChunk(cx, cy, chunkRGB); } else { this.fetchTile(zoom, cx, cy, chunkRGB); } @@ -208,7 +190,7 @@ class ChunkLoader { ? `${historicalDate}${historicalTime}` : historicalDate; chunkKey += `:${cx}:${cy}`; - const chunk = this.chunks.get(chunkKey); + const chunk = this.cget(chunkKey); const { canvasId } = this; if (chunk) { if (chunk.ready) { @@ -222,19 +204,18 @@ class ChunkLoader { this.historicalMaxTiledZooms, ); // fetch tile - const chunkRGB = new ChunkRGB( + const chunkRGB = new Chunk( this.palette, historicalCanvasMaxTiledZoom, cx, cy, ); - this.chunks.set(chunkKey, chunkRGB); + this.cset(chunkKey, chunkRGB); this.fetchHistoricalChunk( cx, cy, historicalDate, historicalTime, - historicalCanvasMaxTiledZoom, chunkRGB, ); } @@ -246,12 +227,10 @@ class ChunkLoader { cy, historicalDate, historicalTime, - historicalCanvasMaxTiledZoom, chunkRGB, ) { const { canvasId } = this; - const center = [historicalCanvasMaxTiledZoom, cx, cy]; // eslint-disable-next-line max-len let url = `${window.ssv.backupurl}/${historicalDate.slice(0, 4)}/${historicalDate.slice(4, 6)}/${historicalDate.slice(6)}/`; if (historicalTime) { @@ -261,13 +240,13 @@ class ChunkLoader { // full tiles url += `${canvasId}/tiles/${cx}/${cy}.png`; } - this.store.dispatch(requestBigChunk(center)); + this.bcReqChunk(chunkRGB); try { const img = await loadImage(url); chunkRGB.fromImage(img); - this.store.dispatch(receiveBigChunk(center, chunkRGB)); + this.bcRecChunk(chunkRGB); } catch (error) { - this.store.dispatch(receiveBigChunkFailure(center, error.message)); + this.bcReqChunkFail(chunkRGB, error); if (historicalTime) { chunkRGB.empty(true); } else { @@ -276,9 +255,8 @@ class ChunkLoader { } } - async fetchBaseChunk(zoom, cx, cy, chunkRGB) { - const center = [zoom, cx, cy]; - this.store.dispatch(requestBigChunk(center)); + async fetchBaseChunk(cx, cy, chunkRGB) { + this.bcReqChunk(chunkRGB); try { const url = `${shardOrigin}/chunks/${this.canvasId}/${cx}/${cy}.bmp`; const response = await fetch(url); @@ -290,30 +268,30 @@ class ChunkLoader { } else { throw new Error('Chunk response was invalid'); } - this.store.dispatch(receiveBigChunk(center, chunkRGB)); + this.bcRecChunk(chunkRGB); } else { throw new Error('Network response was not ok.'); } } catch (error) { chunkRGB.empty(); - this.store.dispatch(receiveBigChunkFailure(center, error.message)); + chunkRGB.recUpdates = true; + this.bcReqChunkFail(chunkRGB, error); } } async fetchTile(zoom, cx, cy, chunkRGB) { - const center = [zoom, cx, cy]; - this.store.dispatch(requestBigChunk(center)); + this.bcReqChunk(chunkRGB); try { // eslint-disable-next-line max-len const url = `/tiles/${this.canvasId}/${zoom}/${cx}/${cy}.webp`; const img = await loadImage(url); chunkRGB.fromImage(img); - this.store.dispatch(receiveBigChunk(center, chunkRGB)); + this.bcRecChunk(chunkRGB); } catch (error) { - this.store.dispatch(receiveBigChunkFailure(center, error.message)); chunkRGB.empty(); + this.bcReqChunkFail(chunkRGB, error); } } } -export default ChunkLoader; +export default ChunkLoader2D; diff --git a/src/ui/ChunkLoader3D.js b/src/ui/ChunkLoader3D.js index 64906bd..b03cd26 100644 --- a/src/ui/ChunkLoader3D.js +++ b/src/ui/ChunkLoader3D.js @@ -3,37 +3,21 @@ * */ -import Chunk from './ChunkRGB3D'; -import { - requestBigChunk, - receiveBigChunk, - receiveBigChunkFailure, -} from '../store/actions'; +import ChunkLoader from './ChunkLoader'; +import Chunk from './Chunk3D'; import { getChunkOfPixel, getOffsetOfPixel, } from '../core/utils'; import { shardOrigin } from '../store/actions/fetch'; -class ChunkLoader { - store = null; - canvasId; +class ChunkLoader3D extends ChunkLoader { palette; - chunks; // Map constructor(store, canvasId, palette, canvasSize) { - this.store = store; - this.canvasId = canvasId; + super(store, canvasId); this.palette = palette; this.canvasSize = canvasSize; - this.chunks = new Map(); - } - - destructor() { - this.chunks.forEach((chunk) => { - chunk.destructor(); - }); - this.chunks = new Map(); } getVoxel(x, y, z) { @@ -41,7 +25,7 @@ class ChunkLoader { const [xc, zc] = getChunkOfPixel(canvasSize, x, y, z); const offset = getOffsetOfPixel(canvasSize, x, y, z); const key = `${xc}:${zc}`; - const chunk = this.chunks.get(key); + const chunk = this.cget(key); if (chunk) { const clr = chunk.getVoxelByOffset(offset); return clr; @@ -49,10 +33,6 @@ class ChunkLoader { return 0; } - getAllChunks() { - return this.chunks; - } - getVoxelUpdate( xc, zc, @@ -60,7 +40,7 @@ class ChunkLoader { color, ) { const key = `${xc}:${zc}`; - const chunk = this.chunks.get(key); + const chunk = this.cget(key); if (chunk) { chunk.setVoxelByOffset(offset, color); } @@ -69,10 +49,10 @@ class ChunkLoader { getChunk(xc, zc, fetch) { const chunkKey = `${xc}:${zc}`; // console.log(`Get chunk ${chunkKey}`); - let chunk = this.chunks.get(chunkKey); + let chunk = this.cget(chunkKey); if (chunk) { if (chunk.ready) { - chunk.timestamp = Date.now(); + chunk.touch(); return chunk.mesh; } return null; @@ -80,15 +60,14 @@ class ChunkLoader { if (fetch) { // fetch chunk chunk = new Chunk(this.palette, chunkKey, xc, zc); - this.chunks.set(chunkKey, chunk); + this.cset(chunkKey, chunk); this.fetchChunk(xc, zc, chunk); } return null; } async fetchChunk(cx, cz, chunk) { - const center = [0, cx, cz]; - this.store.dispatch(requestBigChunk(center)); + this.bcReqChunk(chunk); try { const url = `${shardOrigin}/chunks/${this.canvasId}/${cx}/${cz}.bmp`; const response = await fetch(url); @@ -100,15 +79,15 @@ class ChunkLoader { } else { throw new Error('Chunk response was invalid'); } - this.store.dispatch(receiveBigChunk(center, chunk)); + this.bcRecChunk(chunk); } else { throw new Error('Network response was not ok.'); } } catch (error) { chunk.empty(); - this.store.dispatch(receiveBigChunkFailure(center, error)); + this.bcReqChunkFail(chunk, error); } } } -export default ChunkLoader; +export default ChunkLoader3D; diff --git a/src/ui/Renderer.js b/src/ui/Renderer.js new file mode 100644 index 0000000..2430529 --- /dev/null +++ b/src/ui/Renderer.js @@ -0,0 +1,53 @@ +/* + * parent class for Renderer + */ + +/* eslint-disable class-methods-use-this */ + +class Renderer { + store; + // needs to be known for lazy loading THREE + is3D = null; + // chunk loader must be set by subclass + chunkLoader = null; + + constructor(store) { + this.store = store; + } + + get chunks() { + return this.chunkLoader.chunks; + } + + get recChunkIds() { + if (!this.chunkLoader) { + return []; + } + return this.chunkLoader.recChunkIds; + } + + destructor() { + if (this.chunkLoader) { + this.chunkLoader.destructor(); + } + } + + render() {} + + renderPixel() {} + + updateCanvasData() {} + + isChunkInView() { + return true; + } + + gc() { + if (!this.chunkLoader) { + return; + } + this.chunkLoader.gc(this); + } +} + +export default Renderer; diff --git a/src/ui/Renderer2D.js b/src/ui/Renderer2D.js index f37089d..3e92ce1 100644 --- a/src/ui/Renderer2D.js +++ b/src/ui/Renderer2D.js @@ -18,14 +18,14 @@ import { } from './render2Delements'; import PixelPainterControls from '../controls/PixelPainterControls'; +import Renderer from './Renderer'; import ChunkLoader from './ChunkLoader2D'; import pixelNotify from './PixelNotify'; -class Renderer { +class Renderer2D extends Renderer { is3D = false; // canvasId = null; - chunkLoader = null; //-- centerChunk; tiledScale; @@ -33,7 +33,6 @@ class Renderer { hover; //-- viewport = null; - store; //-- forceNextRender; forceNextSubrender; @@ -43,6 +42,7 @@ class Renderer { oldHistoricalTime; constructor(store) { + super(store); this.centerChunk = [null, null]; this.tiledScale = 0; this.tiledZoom = 4; @@ -71,23 +71,23 @@ class Renderer { context.fillStyle = '#000000'; context.fillRect(0, 0, this.canvas.width, this.canvas.height); //-- - this.setStore(store); + const state = store.getState(); + this.updateCanvasData(state); + this.updateScale(state); + this.controls = new PixelPainterControls(this, this.viewport, store); } destructor() { this.controls.dispose(); window.removeEventListener('resize', this.onWindowResize); this.viewport.remove(); + super.destructor(); } getViewport() { return this.viewport; } - getAllChunks() { - return this.chunkLoader.getAllChunks(); - } - onWindowResize() { this.viewport.width = window.innerWidth; this.viewport.height = window.innerHeight; @@ -111,15 +111,6 @@ class Renderer { this.forceNextRender = true; } - // HAS to be set before any rendering can happen - setStore(store) { - this.store = store; - const state = store.getState(); - this.updateCanvasData(state); - this.updateScale(state); - this.controls = new PixelPainterControls(this, this.viewport, store); - } - updateCanvasData(state) { const { canvasId, @@ -732,4 +723,4 @@ class Renderer { } -export default Renderer; +export default Renderer2D; diff --git a/src/ui/Renderer3D.js b/src/ui/Renderer3D.js index 9900476..ff79b48 100644 --- a/src/ui/Renderer3D.js +++ b/src/ui/Renderer3D.js @@ -8,6 +8,7 @@ import Sky from './Sky'; import InfiniteGridHelper from './InfiniteGridHelper'; import VoxelPainterControls from '../controls/VoxelPainterControls'; +import Renderer from './Renderer'; import ChunkLoader from './ChunkLoader3D'; import { getChunkOfPixel, @@ -24,11 +25,9 @@ import pixelTransferController from './PixelTransferController'; const renderDistance = 150; -class Renderer { +class Renderer3D extends Renderer { is3D = true; //-- - store; - //-- scene; camera; rollOverMesh; @@ -48,14 +47,12 @@ class Renderer { pressCdTime; multitap; //-- - chunkLoader = null; forceNextRender = false; constructor(store) { - this.store = store; + super(store); const state = store.getState(); this.objects = []; - this.chunkLoader = null; // camera const camera = new THREE.PerspectiveCamera( @@ -212,9 +209,7 @@ class Renderer { const { domElement } = this.threeRenderer; this.threeRenderer = null; domElement.remove(); - if (this.chunkLoader) { - this.chunkLoader.destructor(); - } + super.destructor(); } updateView() { @@ -266,11 +261,6 @@ class Renderer { return null; } - // TODO check if GC even works on 3D canvas - getAllChunks() { - return this.chunkLoader.getAllChunks(); - } - renderPixel( i, j, @@ -669,4 +659,4 @@ class Renderer { } } -export default Renderer; +export default Renderer3D; diff --git a/src/ui/renderer.js b/src/ui/rendererFactory.js similarity index 88% rename from src/ui/renderer.js rename to src/ui/rendererFactory.js index dc790db..fcc2155 100644 --- a/src/ui/renderer.js +++ b/src/ui/rendererFactory.js @@ -7,17 +7,12 @@ import { t } from 'ttag'; +import Renderer from './Renderer'; import Renderer2D from './Renderer2D'; import { pAlert } from '../store/actions'; import { isWebGL2Available } from '../core/utils'; -const dummyRenderer = { - is3D: null, - render: () => null, - destructor: () => null, - renderPixel: () => null, - updateCanvasData: () => null, -}; +const dummyRenderer = new Renderer(); let renderer = dummyRenderer;