このスライドは 2013-11-30 に開催された HTML5 Conference 2013 で
地下鉄・サクサク・これからのWebゲームアプリが備えるべき8つの機能
としてお話したものです
Next Mobile WebApplicationuupaa2013-11-30
View Slide
Me@uupaa - 2012 joinR&DApp
2013-11-30 HTML5 Conference 2013Web8
WebApp ?WebAppWebApp, Browser Game,SinglePageApplication, ES6, Storage, Cache,Audio, Canvas, WebWorker
NativeApp vs WebAppNativeAppWebApp ?3D
WebApp
Single Page Application (SPA)SPA ?1WebApp
SPA(BGM)WebApp800ms -> 200ms
SPA
Audio/WebAudioAudioBGMBGM SEAndroid
WebAudioWebAudio m4a(AAC)WebAudio SoundSprite
WebAudio( )(input)( )( )(JavaScript )
+-------+ +-------+ +-------+ +-------+ +-------+| SE[0] | | SE[1] | | SE[n] | | BGM-A | | BGM-B | BufferSource <>+---+---+ +---+---+ +---+---+ +---+---+ +---+---+| | | | || | | +----+---+ +---+----+| | | | FADE-A | | FADE-B | BGM CrossFade <>| | | +----+---+ +---+----++---------+---------+ +----+----+| |+----+---+ +--+---+| EFFECT | | LOOP | Effect/Loop Volume <>+----+---+ +--+---++-------------+------------++---+---+| MUTE | Mute <>+---+---++------+------+| Compressor | <>+------+------++------+------+| Distination | AudioContext.Distination+-------------+
var ctx = new global.AudioContext();var node = {compressor: null,fade: [null,null], // CrossFade volumemaster: null, // master volumeeffect: null, // effect volumeloop: null, // loop volumemute: null // mute};node.compressor = ctx.createDynamicsCompressor();node.mute = ctx.createGainNode();node.master = ctx.createGainNode();node.effect = ctx.createGainNode();node.loop = ctx.createGainNode();node.fade[0] = ctx.createGainNode();node.fade[1] = ctx.createGainNode();node.fade[0].connect(node.loop);node.fade[1].connect(node.loop);node.effect.connect(node.master);node.loop.connect(node.master);node.master.connect(node.mute);node.mute.connect(node.compressor);node.compressor.connect(ctx.destination);
( MIDI)
WebAudioWebSE BGMOFFON/OFFAndroid DualCore, 1GB RAMiOS iOS 7, iPhone 4S (iOS 6 )Chrome for Android 31http://hello.uupaa.net/issues/2/HE-AAC
( )
Canvas65 70% 200dpi (in Google Play)$179 (Moto G)326dpi
CanvasCanvasRenderingContext2D#toDataURL96dpi (ImageData)High DefinitiontoDataURLHDtoBlobHDcreateImageDataHDgetImageDataHDputImageDataHD
2013 ImageDataWidth x Height Canvas ImageData Device480 x 320 600 KB iPhone (2007)960 x 640 2.3 MB iPhone Retina (2010)2048 x 1536 12 MB iPad Retina (2012)ImageData Pixel x 4byte(RGBA)
2015 ImageData2015 4KWidth x Height Canvas ImageData Device960 x 640 2.3 MB iPhone Retina (2010)2048 x 1536 12 MB iPad Retina (2012)2560 x 1440 14 MB 2K Android (maybe 2014)3840 x 2160 31.6 MB 4K Android (maybe 2015)
ImageDataImageDataMB 16ms …for
?WebWorker
WebWorker Canvas ? ?Canvascanvas.transferControlToProxycanvasProxy.setContextcontext.commit
CanvasProxy (1 )// index.jsvar canvas = document.querySelector("canvas");var canvasProxy = canvas.transferControlToProxy(); // CanvasProxy を取得var canvasWorker = new Worker("BackgroundCanvasRender.js");canvasWorker.postMessage(canvasProxy, [canvasProxy]); // Worker に渡す// BackgroundCanvasRender.jsonmessage = function(event) {var context = new CanvasRenderingContext2D();var canvasProxy = event.data;canvasProxy.setContext(context); // bindsetInterval(function() {context.clearRect(0, 0, context.width, context.height);context.fillText(new Date() + "", 0, 100);context.commit(); // render}, 1000);};
?100 150MBGCwindow.gc() Worker CanvasdrawImage …
High DefinitionCanvasProxy + WebWorker
Command Pattern( )
Canvas API// beforectx.fillStyle = "#fff";ctx.fillRect(0, 0, 100, 100);// aftervar canvasCommands = [["fillStyle", "#fff"],["fillRect", 0, 0, 100, 100]];for (var i = 0, iz = canvasCommands.length; i < iz; ++i) {switch (canvasCommands[i]) {case "fillStyle": ... break;case "fillRect": ... break;}});
:Android ( ), ,
( )Android Browser, Chrome for Android( )
DrawCall ( )Canvas APIDrawCallDrawCall// beforefunction drawCall1() {ctx.fillRect(...);}function drawCall2() {ctx.fillRect(...);}drawCall1();heavyRoutine();drawCall2();Chrome Canvas profilerDrawCall CanvasAPI
+ SnapShot, Movie
+ Remote PlayDOM, CSS, Audio, CanvasApp
+ WebWorkerWebWorker postMessage// index.jsvar worker = new Worker("Worker.js");var request = [];worker.onmessage = function(event) {var response = event.data.response;:};worker.postMessage({ request: request });// Worker.jsonmessage = function(event) {var request = event.data.request;postMessage({ response: ["ok"], error: null });};
WebWorker
WebAppWebWorker ?, ,,,
WebWorker JavaScript?
WebWorker( )
importScriptsnavigator.userAgent, onLineJSON, BLOB, FileReader, FileReaderSyncTimer - setTimeout(), setInterval()encodeURIComponent/decodeURIComponentTypedArrayMessageChannelWebSQL, WebSQLSyncWebSocket, XMLHttpRequest
navigator.webkitPersistentStoragenavigator.webkitTemporaryStorageHighPerformanceTimer - performance.now()TextEncoder/TextDecoderCrypto, Base64(atob, btoa)ImageBitmapIndexedDBWorkerConsole - console.log()RequestFileSystem, RequestFileSystemSyncES6( Symbol, Set, Map, WeakMap, WeakSet, Promise )
postMessage ?Nexus 7(2012) Chrome 0.6ms,Mac Chrome 0.02msvar worker = new Worker("worker.js");var score = 0;worker.onmessage = function(event) {worker.postMessage(++score);};setTimeout(function() { worker.postMessage("stop"); }, 10 * 1000);// worker.jsvar payload = [];var timerID = setInterval(function() {postMessage(payload);}, 1);onmessage = function(event) {event.data === "stop" && clearInterval(timerID);};
postMessageStructured Cloningvar payload = new Array(big number);postMessage(payload);1MB (payload)32MB payload25%var MB = 1024 * 1024;var payload = new Uint8ClampedArray( 1 * MB); // 無負荷時の 98% の速度で動作var payload = new Uint8ClampedArray( 8 * MB); // 無負荷時の 90% の速度で動作var payload = new Uint8ClampedArray(32 * MB); // 無負荷時の 25% の速度で動作Structured Cloning
Transferable ObjectspostMessage(zero-copy)Transferable Objectsvar payload = new Array(big number);postMessage(payload, [payload]);Transferable Objects
WebWorkerimportScriptJavaScript PrototypeWebWorker
+ ModuleNode.js, Browser, WebWorker(function(global) {// --- define ----------------------------------------------// --- variable --------------------------------------------// --- interface -------------------------------------------function Class() {}// --- implement -------------------------------------------// --- export ----------------------------------------------if (global.process) {module.exports = Class;}global.Class = Class;})(this.self || global);: Typical JavaScript Module Pattern
WebWorker ?iframe
Storage, Cache, OfflineStorageLocalStorage 5MB…WebSQL50MBApplicationCache
LocalStorage + WebSQLBase64DataURI
Asset Manifest304 Not ModifiedHTTP1 (SPDY )5MB + 5MB
Asset Manifes SampleID URLMimeTypeHash (MD5, SHA1 ),{"HelloAssetManifest": {"url": "asset/scene/Hello.js","hash": "fd2b04","mime": "scene/class","size": 1425,"prime": 1}}
StorageIndexed DB + Disk Quota Management API10%ServiceWorkerURL…
…デモ1 30MB (140kB jpg x 210 )Base64 1 40 50MB13 (600 800MB)
SinglePageApplicationWebSQL LocalStorageAsset ManifestiframeBase64 Doubler1(Bulk Download)
DoublerBase64 ASCIISQLite(LocalStorage WebSQL )UTF16Doubler NULL, BOM,UTF16Base64 200 250%
Doubler Code Point// Doubler.js: UTF16 Safe packer// Mobile Browser unavailable UTF16 words:// Safari: NULL, BOM// Chrome: NULL, BOM, SurrogatePairs// Android: NULL//// Desktop Browser unavailable UTF16 words:// Safari: NULL, BOM// Chrome: NULL, BOM, SurrogatePairs//// +- UINT16 -+- Doubler.pack() -+- unpack -+// | 0x0000 | 0x0020, 0x8000 | -0x8000 | encode NULL// +----------+------------------+----------+// | 0x0020 | 0x0020, 0x8020 | -0x8000 | encode 0x20// +----------+------------------+----------+// | 0xd800 | 0x0020, 0x5800 | +0x8000 | encode SurrogatePairs// | : | : | |// | 0xdfff | 0x0020, 0x5fff | |// +----------+------------------+----------+// | 0xfffe | 0x0020, 0x7ffe | +0x8000 | encode BOM// | 0xffff | 0x0020, 0x7fff | |// +-- Tail --+- Doubler.pack() -+----------+// | 0x00 | 0x0020, 0x9000 | -0x9000 | encode Tail byte// | : | : | |// | 0xff | 0x0020, 0x90ff | |// +----------+------------------+----------+
Bulk DownloadGIFAssetSprite1?(SPDY )