make hook for renderer to store
This commit is contained in:
parent
c6dabe8a3e
commit
cfbae41817
|
@ -41,7 +41,7 @@ import onKeyPress from './ui/keypress';
|
|||
|
||||
import App from './components/App';
|
||||
|
||||
import Renderer from './ui/Renderer';
|
||||
import renderer from './ui/Renderer';
|
||||
import ProtocolClient from './socket/ProtocolClient';
|
||||
|
||||
window.addEventListener('keydown', onKeyPress, false);
|
||||
|
@ -190,8 +190,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
);
|
||||
|
||||
const viewport = initViewport();
|
||||
const renderer = new Renderer();
|
||||
renderer.setViewport(viewport);
|
||||
renderer.setViewport(viewport, store);
|
||||
|
||||
ProtocolClient.on('pixelUpdate', ({
|
||||
i, j, offset, color,
|
||||
|
|
|
@ -8,6 +8,7 @@ import { persistStore } from 'redux-persist';
|
|||
import audio from './audio';
|
||||
import swal from './sweetAlert';
|
||||
import protocolClientHook from './protocolClientHook';
|
||||
import rendererHook from './rendererHook';
|
||||
// import ads from './ads';
|
||||
// import analytics from './analytics';
|
||||
import array from './array';
|
||||
|
@ -38,6 +39,7 @@ const store = createStore(
|
|||
notifications,
|
||||
title,
|
||||
protocolClientHook,
|
||||
rendererHook,
|
||||
// ads,
|
||||
// analytics,
|
||||
logger,
|
||||
|
|
53
src/store/rendererHook.js
Normal file
53
src/store/rendererHook.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Hooks for renderer
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import renderer from '../ui/Renderer';
|
||||
|
||||
export default (store) => (next) => (action) => {
|
||||
// executed after reducers
|
||||
const ret = next(action);
|
||||
|
||||
const state = store.getState();
|
||||
|
||||
switch (action.type) {
|
||||
case 'RELOAD_URL':
|
||||
case 'SELECT_CANVAS':
|
||||
case 'RECEIVE_ME': {
|
||||
renderer.updateCanvasData(state);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'REQUEST_BIG_CHUNK':
|
||||
case 'RECEIVE_BIG_CHUNK':
|
||||
case 'RECEIVE_BIG_CHUNK_FAILURE':
|
||||
case 'RECEIVE_IMAGE_TILE': {
|
||||
renderer.forceNextRender = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'SET_SCALE': {
|
||||
const {
|
||||
viewscale,
|
||||
canvasMaxTiledZoom,
|
||||
view,
|
||||
canvasSize,
|
||||
} = state.canvas;
|
||||
renderer.updateScale(viewscale, canvasMaxTiledZoom, view, canvasSize);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'SET_VIEW_COORDINATES': {
|
||||
const { view, canvasSize } = state.canvas;
|
||||
renderer.updateView(view, canvasSize);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// nothing
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
|
@ -11,7 +11,6 @@ import {
|
|||
getTileOfPixel,
|
||||
getPixelFromChunkOffset,
|
||||
} from '../core/utils';
|
||||
import store from './store';
|
||||
import { fetchChunk, fetchTile } from '../actions';
|
||||
|
||||
import {
|
||||
|
@ -35,31 +34,25 @@ const SCALE_THREASHOLD = Math.min(
|
|||
|
||||
|
||||
class Renderer {
|
||||
lastFetchs: number;
|
||||
centerChunk: Cell;
|
||||
view: Cell;
|
||||
tiledScale: number;
|
||||
tiledZoom: number;
|
||||
hover: boolean;
|
||||
canvasId: number;
|
||||
//--
|
||||
viewport: HTMLCanvasElement = null;
|
||||
store;
|
||||
//--
|
||||
scale: number;
|
||||
forceNextRender: boolean;
|
||||
forceNextSubrender: boolean;
|
||||
canvas: HTMLCanvasElement;
|
||||
lastFetch: number;
|
||||
|
||||
constructor() {
|
||||
this.lastFetchs = 0;
|
||||
this.centerChunk = [null, null];
|
||||
this.tiledScale = 0;
|
||||
this.tiledZoom = 4;
|
||||
this.hover = false;
|
||||
this.canvasId = null;
|
||||
//--
|
||||
this.scale = 0;
|
||||
this.forceNextRender = true;
|
||||
this.forceNextSubrender = true;
|
||||
this.lastFetch = 0;
|
||||
|
@ -76,33 +69,95 @@ class Renderer {
|
|||
}
|
||||
|
||||
// HAS to be set before any rendering can happen
|
||||
setViewport(viewport: HTMLCanvasElement) {
|
||||
setViewport(viewport: HTMLCanvasElement, store) {
|
||||
this.viewport = viewport;
|
||||
this.store = store;
|
||||
const state = store.getState();
|
||||
const {
|
||||
canvasMaxTiledZoom,
|
||||
viewscale,
|
||||
view,
|
||||
canvasSize,
|
||||
} = state.canvas;
|
||||
this.updateCanvasData(state);
|
||||
this.updateScale(viewscale, canvasMaxTiledZoom, view, canvasSize);
|
||||
this.forceNextRender = true;
|
||||
}
|
||||
|
||||
updateCanvasData(state: State) {
|
||||
const {
|
||||
canvasMaxTiledZoom,
|
||||
viewscale,
|
||||
view,
|
||||
canvasSize,
|
||||
} = state.canvas;
|
||||
this.tiledZoom = canvasMaxTiledZoom + Math.log2(this.tiledScale) / 2;
|
||||
this.updateScale(viewscale, canvasMaxTiledZoom, view, canvasSize);
|
||||
}
|
||||
|
||||
updateScale(
|
||||
viewscale: number,
|
||||
canvasMaxTiledZoom: number,
|
||||
view,
|
||||
canvasSize,
|
||||
) {
|
||||
pixelNotify.updateScale(viewscale);
|
||||
let tiledScale = (viewscale > 0.5) ? 0 : Math.round(Math.log2(viewscale) / 2);
|
||||
tiledScale = 4 ** tiledScale;
|
||||
const tiledZoom = canvasMaxTiledZoom + Math.log2(tiledScale) / 2;
|
||||
const relScale = viewscale / tiledScale;
|
||||
|
||||
this.tiledScale = tiledScale;
|
||||
this.tiledZoom = tiledZoom;
|
||||
this.relScale = relScale;
|
||||
this.updateView(view, canvasSize);
|
||||
}
|
||||
|
||||
updateView(view, canvasSize) {
|
||||
const [x, y] = view;
|
||||
let [cx, cy] = this.centerChunk;
|
||||
const [curcx, curcy] = getTileOfPixel(this.tiledScale, [x, y], canvasSize);
|
||||
if (cx !== curcx || cy !== curcy) {
|
||||
cx = curcx;
|
||||
cy = curcy;
|
||||
this.centerChunk = [cx, cy];
|
||||
this.forceNextRender = true;
|
||||
} else {
|
||||
this.forceNextSubrender = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
renderPixel(
|
||||
i: number,
|
||||
j: number,
|
||||
offset: number,
|
||||
color: ColorIndex,
|
||||
) {
|
||||
if (this.scale < 0.8) return;
|
||||
const scale = (this.scale > SCALE_THREASHOLD) ? 1 : this.scale;
|
||||
const state: State = this.store.getState();
|
||||
const {
|
||||
canvasSize,
|
||||
palette,
|
||||
scale,
|
||||
} = state.canvas;
|
||||
|
||||
if (scale < 0.8) return;
|
||||
const scaleM = (scale > SCALE_THREASHOLD) ? 1 : scale;
|
||||
|
||||
const context = this.canvas.getContext('2d');
|
||||
if (!context) return;
|
||||
|
||||
const [x, y] = getPixelFromChunkOffset(i, j, offset, this.canvasSize);
|
||||
const [x, y] = getPixelFromChunkOffset(i, j, offset, canvasSize);
|
||||
|
||||
const [canX, canY] = this.centerChunk
|
||||
.map((z) => (z + 0.5) * TILE_SIZE - this.canvasSize / 2);
|
||||
const px = ((x - canX) * scale) + (CANVAS_WIDTH / 2);
|
||||
const py = ((y - canY) * scale) + (CANVAS_HEIGHT / 2);
|
||||
.map((z) => (z + 0.5) * TILE_SIZE - canvasSize / 2);
|
||||
const px = ((x - canX) * scaleM) + (CANVAS_WIDTH / 2);
|
||||
const py = ((y - canY) * scaleM) + (CANVAS_HEIGHT / 2);
|
||||
// if not part of our current canvas, do not render
|
||||
if (px < 0 || py >= CANVAS_WIDTH || py < 0 || py >= CANVAS_HEIGHT) return;
|
||||
|
||||
context.fillStyle = this.palette.colors[color];
|
||||
context.fillRect(px, py, scale, scale);
|
||||
context.fillStyle = palette.colors[color];
|
||||
context.fillRect(px, py, scaleM, scaleM);
|
||||
pixelNotify.addPixel(x, y);
|
||||
|
||||
this.forceNextSubrender = true;
|
||||
|
@ -132,16 +187,25 @@ class Renderer {
|
|||
|
||||
|
||||
renderChunks(
|
||||
chunks,
|
||||
view: Cell,
|
||||
canvasId: number,
|
||||
state: State,
|
||||
) {
|
||||
const context = this.canvas.getContext('2d');
|
||||
if (!context) return;
|
||||
|
||||
const {
|
||||
centerChunk: chunkPosition, scale, tiledScale, tiledZoom,
|
||||
centerChunk: chunkPosition,
|
||||
tiledScale,
|
||||
tiledZoom,
|
||||
viewport,
|
||||
} = this;
|
||||
const {
|
||||
viewscale: scale,
|
||||
canvasId,
|
||||
canvasSize,
|
||||
canvasMaxTiledZoom,
|
||||
chunks,
|
||||
} = state.canvas;
|
||||
|
||||
let { relScale } = this;
|
||||
// clear rect is just needed for Google Chrome, else it would flash regularly
|
||||
context.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
|
||||
|
@ -159,7 +223,7 @@ class Renderer {
|
|||
}
|
||||
// define how many chunks we will render
|
||||
// don't render chunks outside of viewport
|
||||
const { width, height } = this.viewport;
|
||||
const { width, height } = viewport;
|
||||
const CHUNK_RENDER_RADIUS_X = Math.ceil(width / TILE_SIZE / 2 / relScale);
|
||||
const CHUNK_RENDER_RADIUS_Y = Math.ceil(height / TILE_SIZE / 2 / relScale);
|
||||
// If scale is so large that neighbouring chunks wouldn't fit in canvas,
|
||||
|
@ -194,7 +258,7 @@ class Renderer {
|
|||
const x = xOffset + dx * TILE_SIZE;
|
||||
const y = yOffset + dy * TILE_SIZE;
|
||||
|
||||
const chunkMaxXY = this.canvasSize / TILE_SIZE;
|
||||
const chunkMaxXY = canvasSize / TILE_SIZE;
|
||||
if (cx < 0 || cx >= chunkMaxXY * tiledScale || cy < 0 || cy >= chunkMaxXY * tiledScale) {
|
||||
// if out of bounds
|
||||
context.fillRect(x, y, TILE_SIZE, TILE_SIZE);
|
||||
|
@ -214,10 +278,10 @@ class Renderer {
|
|||
} else {
|
||||
// we don't have that chunk
|
||||
if (fetch) {
|
||||
if (tiledZoom === this.canvasMaxTiledZoom) {
|
||||
store.dispatch(fetchChunk(canvasId, [tiledZoom, cx, cy]));
|
||||
if (tiledZoom === canvasMaxTiledZoom) {
|
||||
this.store.dispatch(fetchChunk(canvasId, [tiledZoom, cx, cy]));
|
||||
} else {
|
||||
store.dispatch(fetchTile(canvasId, [tiledZoom, cx, cy]));
|
||||
this.store.dispatch(fetchTile(canvasId, [tiledZoom, cx, cy]));
|
||||
}
|
||||
}
|
||||
if (loadingTiles.hasTiles) {
|
||||
|
@ -233,9 +297,14 @@ class Renderer {
|
|||
}
|
||||
|
||||
|
||||
// keep in mind that everything we got here gets executed 60 times per second
|
||||
// avoiding unneccessary stuff is important
|
||||
render() {
|
||||
const { viewport } = this;
|
||||
const state: State = store.getState();
|
||||
try{
|
||||
const {
|
||||
viewport,
|
||||
} = this;
|
||||
const state: State = this.store.getState();
|
||||
const {
|
||||
showGrid,
|
||||
showPixelNotify,
|
||||
|
@ -247,57 +316,28 @@ class Renderer {
|
|||
placeAllowed,
|
||||
} = state.user;
|
||||
const {
|
||||
chunks,
|
||||
view,
|
||||
fetchs,
|
||||
viewscale,
|
||||
canvasSize,
|
||||
canvasId,
|
||||
} = state.canvas;
|
||||
|
||||
if (!view || canvasId === null) return;
|
||||
|
||||
if (this.canvasId !== canvasId) {
|
||||
const { canvasSize, palette, canvasMaxTiledZoom } = state.canvas;
|
||||
this.canvasSize = canvasSize;
|
||||
this.palette = palette;
|
||||
this.canvasMaxTiledZoom = canvasMaxTiledZoom;
|
||||
this.tiledZoom = this.canvasMaxTiledZoom + Math.log2(this.tiledScale) / 2;
|
||||
}
|
||||
|
||||
const [x, y] = view;
|
||||
let [cx, cy] = this.centerChunk;
|
||||
const [cx, cy] = this.centerChunk;
|
||||
|
||||
// if we have to render pixelnotify
|
||||
const doRenderPixelnotify = (viewscale >= 0.5 && showPixelNotify && pixelNotify.doRender());
|
||||
// if we have to render offscreen canvas
|
||||
let doRenderCanvas = (this.lastFetchs !== fetchs || this.forceNextRender);
|
||||
if (viewscale !== this.scale) {
|
||||
this.scale = viewscale;
|
||||
pixelNotify.updateScale(viewscale);
|
||||
this.tiledScale = (viewscale > 0.5) ? 0 : Math.round(Math.log2(viewscale) / 2);
|
||||
this.tiledScale = 4 ** this.tiledScale;
|
||||
this.tiledZoom = this.canvasMaxTiledZoom + Math.log2(this.tiledScale) / 2;
|
||||
this.relScale = viewscale / this.tiledScale;
|
||||
doRenderCanvas = true;
|
||||
}
|
||||
// if we have to render placeholder
|
||||
const doRenderPlaceholder = (viewscale >= 3 && placeAllowed && (hover || this.hover) && !isPotato);
|
||||
const doRenderPotatoPlaceholder = (viewscale >= 3 && placeAllowed && (hover !== this.hover || doRenderCanvas || this.forceNextSubrender || doRenderPixelnotify) && isPotato);
|
||||
const doRenderPotatoPlaceholder = (viewscale >= 3 && placeAllowed && (hover !== this.hover || this.forceNextRender || this.forceNextSubrender || doRenderPixelnotify) && isPotato);
|
||||
//--
|
||||
if (view !== this.view) {
|
||||
const [curcx, curcy] = getTileOfPixel(this.tiledScale, [x, y], this.canvasSize);
|
||||
if (cx !== curcx || cy !== curcy) {
|
||||
cx = curcx;
|
||||
cy = curcy;
|
||||
this.centerChunk = [cx, cy];
|
||||
doRenderCanvas = true;
|
||||
}
|
||||
this.view = view;
|
||||
// if we have nothing to render, return
|
||||
// note: this.hover is used to, to render without the placeholder one last time when cursor leaves window
|
||||
} else if (
|
||||
if (
|
||||
// no full rerender
|
||||
!doRenderCanvas
|
||||
!this.forceNextRender
|
||||
// no render placeholder under cursor
|
||||
&& !doRenderPlaceholder
|
||||
&& !doRenderPotatoPlaceholder
|
||||
|
@ -310,9 +350,8 @@ class Renderer {
|
|||
}
|
||||
this.hover = hover;
|
||||
|
||||
if (doRenderCanvas) {
|
||||
this.renderChunks(chunks, view, canvasId);
|
||||
this.lastFetchs = fetchs;
|
||||
if (this.forceNextRender) {
|
||||
this.renderChunks(state);
|
||||
}
|
||||
this.forceNextRender = false;
|
||||
this.forceNextSubrender = false;
|
||||
|
@ -327,7 +366,8 @@ class Renderer {
|
|||
viewportCtx.imageSmoothingEnabled = false;
|
||||
// If scale is so large that neighbouring chunks wouldn't fit in offscreen canvas,
|
||||
// do scale = 1 in renderChunks and scale in render()
|
||||
const canvasCenter = this.canvasSize / 2;
|
||||
const canvasCenter = canvasSize / 2;
|
||||
console.log("do render")
|
||||
if (viewscale > SCALE_THREASHOLD) {
|
||||
viewportCtx.save();
|
||||
viewportCtx.scale(viewscale, viewscale);
|
||||
|
@ -347,8 +387,12 @@ class Renderer {
|
|||
|
||||
if (hover && doRenderPlaceholder) renderPlaceholder(state, viewport, viewscale);
|
||||
if (hover && doRenderPotatoPlaceholder) renderPotatoPlaceholder(state, viewport, viewscale);
|
||||
} catch {
|
||||
console.log("error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default Renderer;
|
||||
const renderer = new Renderer();
|
||||
export default renderer;
|
||||
|
|
Loading…
Reference in New Issue
Block a user