make hook for renderer to store

This commit is contained in:
HF 2020-01-09 01:48:35 +01:00
parent c6dabe8a3e
commit cfbae41817
4 changed files with 168 additions and 70 deletions

View File

@ -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,

View File

@ -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
View 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;
};

View File

@ -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;