UNPKG

xgpu

Version:

XGPU is an extendable library for WebGPU that provides a higher-level, easy-to-use interface for building rendering engines or processing numeric data. It handles automatic data binding, buffer alignment, variable declarations in shaders, and more. XGPU i

919 lines (915 loc) 31.2 kB
// Copyright (c) 2023 Thomas Le Coz. All rights reserved. // This code is governed by an MIT license that can be found in the LICENSE file. //import { mat3, mat4, vec3 } from "wgpu-matrix"; import { mat4, vec3 } from "gl-matrix"; import { GPUType } from "./GPUType"; export class PrimitiveFloatUniform extends Float32Array { static ON_CHANGE = "ON_CHANGE"; static ON_CHANGED = "ON_CHANGED"; //public uniform: Uniform; name; type; startId = 0; globalStartId = 0; onChange; _mustBeTransfered = true; get mustBeTransfered() { return this._mustBeTransfered; } set mustBeTransfered(b) { //if(this.name == "timeBytes"){ // console.warn(this.name,this._mustBeTransfered,b); //} if (b != this._mustBeTransfered) { //if(b) this.mustTriggerChangeEvent = true; //else this.mustTriggerChangedEvent = true; if (b) this.dispatchEvent(PrimitiveFloatUniform.ON_CHANGE); else this.dispatchEvent(PrimitiveFloatUniform.ON_CHANGED); //if (!b && this.onChange) this.onChange(); this._mustBeTransfered = b; } } //public mustBeTransfered:boolean = true; uniformBuffer; propertyNames; createVariableInsideMain = false; className; constructor(type, val, createLocalVariable = false) { super(val); this.type = new GPUType(type); this.createVariableInsideMain = createLocalVariable; this.className = this.constructor.name; } clone() { const o = new PrimitiveFloatUniform(this.type.rawType, this, this.createVariableInsideMain); o.propertyNames = this.propertyNames; o.className = this.className; o.name = this.name; o.startId = this.startId; //for (let z in this) o[z] = this[z]; return o; } initStruct(propertyNames, createVariableInsideMain = false) { if (this.type.isArray || this.type.isMatrix) throw new Error("initStruct doesn't accept array or matrix"); this.propertyNames = propertyNames; this.createVariableInsideMain = createVariableInsideMain; } createStruct() { //let result = "struct " + this.constructor.name + " {\n"; let result = "struct " + this.className + " {\n"; for (let i = 0; i < this.propertyNames.length; i++) { result += " " + this.propertyNames[i] + ":f32,\n"; } result += "}\n"; return result; } set(m, offset) { super.set(m, offset); this.mustBeTransfered = true; } createVariable(uniformBufferName, name) { if (!this.createVariableInsideMain) return ""; if (!name) name = this.name; let type = this.className; if (type === "Float") type = "f32"; if (type === "Vec2") type = "vec2<f32>"; if (type === "Vec3") type = "vec3<f32>"; if (type === "Vec4") type = "vec4<f32>"; const res = " var " + name + ":" + type + " = " + uniformBufferName + "." + name + ";\n"; //console.log("createVariable = ", res); return res; } update() { } updateStartIdFromParentToChildren() { //used to update the startId of elements contained in an array //|=> by default, the startId is related to the parent, but the parent may have a parent too so we must update every startId } //--------- EVENT DISPATCHER CLASS (that I can't extends because we already extends Float32Array)---- eventListeners = {}; addEventListener(eventName, callback) { if (!this.eventListeners[eventName]) this.eventListeners[eventName] = []; this.eventListeners[eventName].push(callback); } removeEventListener(eventName, callback) { if (this.eventListeners[eventName]) { const id = this.eventListeners[eventName].indexOf(callback); if (id != -1) { this.eventListeners[eventName].splice(id, 1); } } } dispatchEvent(eventName, eventData) { if (this.eventListeners[eventName]) { this.eventListeners[eventName].forEach(callback => { callback(this, eventData); }); } } //--------------------------------------------------------------------------------------------- get definition() { return { type: this.constructor.name, values: this, name: this.name }; } } export class PrimitiveIntUniform extends Int32Array { static ON_CHANGE = "ON_CHANGE"; static ON_CHANGED = "ON_CHANGED"; name; type; startId = 0; globalStartId = 0; onChange; _mustBeTransfered = true; get mustBeTransfered() { return this._mustBeTransfered; } set mustBeTransfered(b) { if (b != this._mustBeTransfered) { if (!b) this.dispatchEvent(PrimitiveIntUniform.ON_CHANGED); else this.dispatchEvent(PrimitiveIntUniform.ON_CHANGE); //if (!b && this.onChange) this.onChange(); this._mustBeTransfered = b; } } uniformBuffer; propertyNames; createVariableInsideMain = false; ; className; constructor(type, val, createLocalVariable = false) { super(val); this.type = new GPUType(type); this.createVariableInsideMain = createLocalVariable; this.className = this.constructor.name; } clone() { return new PrimitiveIntUniform(this.type.rawType, this, this.createVariableInsideMain); } initStruct(propertyNames, createVariableInsideMain = false) { if (this.type.isArray || this.type.isMatrix) throw new Error("initStruct doesn't accept array or matrix"); this.propertyNames = propertyNames; this.createVariableInsideMain = createVariableInsideMain; } createStruct() { let result = "struct " + this.constructor.name + " {\n"; for (let i = 0; i < this.propertyNames.length; i++) { result += " " + this.propertyNames[i] + ":i32,\n"; } result += "}\n"; return result; } updateStartIdFromParentToChildren() { //used to update the startId of elements contained in an array //|=> by default, the startId is related to the parent, but the parent may have a parent too so we must update every startId } createVariable(uniformBufferName) { if (!this.createVariableInsideMain) return ""; let type = this.className; if (type === "Int") type = "i32"; if (type === "IVec2") type = "vec2<i32>"; if (type === "IVec3") type = "vec3<i32>"; if (type === "IVec4") type = "vec4<i32>"; return " var " + this.name + ":" + type + " = " + uniformBufferName + "." + this.name + ";\n"; } /* public feedbackVertexId: number = 0; public feedbackInstanceId: number = 0; public setFeedback(vertexId: number, instanceId: number): PrimitiveIntUniform { this.feedbackVertexId = vertexId; this.feedbackInstanceId = instanceId; return this; }*/ update() { } //--------- EVENT DISPATCHER CLASS (that I can't extends because we already extends Int32Array)---- eventListeners = {}; addEventListener(eventName, callback) { if (!this.eventListeners[eventName]) this.eventListeners[eventName] = []; this.eventListeners[eventName].push(callback); } removeEventListener(eventName, callback) { if (this.eventListeners[eventName]) { const id = this.eventListeners[eventName].indexOf(callback); if (id != -1) { this.eventListeners[eventName].splice(id, 1); } } } dispatchEvent(eventName, eventData) { if (this.eventListeners[eventName]) { this.eventListeners[eventName].forEach(callback => { callback(this, eventData); }); } } //--------------------------------------------------------------------------------------------- get definition() { return { type: this.constructor.name, values: this, name: this.name }; } } export class PrimitiveUintUniform extends Uint32Array { static ON_CHANGE = "ON_CHANGE"; static ON_CHANGED = "ON_CHANGED"; name; type; startId = 0; globalStartId = 0; uniformBuffer; onChange; _mustBeTransfered = true; get mustBeTransfered() { return this._mustBeTransfered; } set mustBeTransfered(b) { if (b != this._mustBeTransfered) { //if (!b && this.onChange) this.onChange(); if (!b) this.dispatchEvent(PrimitiveUintUniform.ON_CHANGED); else this.dispatchEvent(PrimitiveUintUniform.ON_CHANGE); this._mustBeTransfered = b; } } propertyNames; createVariableInsideMain = false; ; className; constructor(type, val, createLocalVariable = false) { super(val); this.type = new GPUType(type); this.createVariableInsideMain = createLocalVariable; this.className = this.constructor.name; } clone() { return new PrimitiveUintUniform(this.type.rawType, this, this.createVariableInsideMain); } initStruct(propertyNames, createVariableInsideMain = false) { if (this.type.isArray || this.type.isMatrix) throw new Error("initStruct doesn't accept array or matrix"); this.propertyNames = propertyNames; this.createVariableInsideMain = createVariableInsideMain; } createStruct() { let result = "struct " + this.constructor.name + " {\n"; for (let i = 0; i < this.propertyNames.length; i++) { result += " " + this.propertyNames[i] + ":u32,\n"; } result += "}\n"; return result; } updateStartIdFromParentToChildren() { //used to update the startId of elements contained in an array //|=> by default, the startId is related to the parent, but the parent may have a parent too so we must update every startId } createVariable(uniformBufferName) { if (!this.createVariableInsideMain) return ""; let type = this.className; if (type === "Uint") type = "u32"; if (type === "UVec2") type = "vec2<u32>"; if (type === "UVec3") type = "vec3<u32>"; if (type === "UVec4") type = "vec4<u32>"; return " var " + this.name + ":" + type + " = " + uniformBufferName + "." + this.name + ";\n"; } /* public feedbackVertexId: number = 0; public feedbackInstanceId: number = 0; public setFeedback(vertexId: number, instanceId: number): PrimitiveUintUniform { this.feedbackVertexId = vertexId; this.feedbackInstanceId = instanceId; return this; } */ update() { } //--------- EVENT DISPATCHER CLASS (that I can't extends because we already extends Uint32Array)---- eventListeners = {}; addEventListener(eventName, callback) { if (!this.eventListeners[eventName]) this.eventListeners[eventName] = []; this.eventListeners[eventName].push(callback); } removeEventListener(eventName, callback) { if (this.eventListeners[eventName]) { const id = this.eventListeners[eventName].indexOf(callback); if (id != -1) { this.eventListeners[eventName].splice(id, 1); } } } dispatchEvent(eventName, eventData) { if (this.eventListeners[eventName]) { this.eventListeners[eventName].forEach(callback => { callback(this, eventData); }); } } //--------------------------------------------------------------------------------------------- get definition() { return { type: this.constructor.name, values: this, name: this.name }; } } //-------------- export class Float extends PrimitiveFloatUniform { constructor(x = 0, createLocalVariable = false) { super("f32", [x], createLocalVariable); } set x(n) { this[0] = n; this.mustBeTransfered = true; } get x() { return this[0]; } } //-------------- export class Vec2 extends PrimitiveFloatUniform { constructor(x = 0, y = 0, createLocalVariable = false) { super("vec2<f32>", [x, y], createLocalVariable); } set x(n) { this[0] = n; this.mustBeTransfered = true; } set y(n) { this[1] = n; this.mustBeTransfered = true; } get x() { return this[0]; } get y() { return this[1]; } } //-------------- export class Vec3 extends PrimitiveFloatUniform { constructor(x = 0, y = 0, z = 0, createLocalVariable = false) { super("vec3<f32>", [x, y, z], createLocalVariable); } set x(n) { this[0] = n; this.mustBeTransfered = true; } set y(n) { this[1] = n; this.mustBeTransfered = true; } set z(n) { this[2] = n; this.mustBeTransfered = true; } get x() { return this[0]; } get y() { return this[1]; } get z() { return this[2]; } } //-------------- export class Vec4 extends PrimitiveFloatUniform { constructor(x = 0, y = 0, z = 0, w = 0, createLocalVariable = false) { super("vec4<f32>", [x, y, z, w], createLocalVariable); } set x(n) { this[0] = n; this.mustBeTransfered = true; } set y(n) { this[1] = n; this.mustBeTransfered = true; } set z(n) { this[2] = n; this.mustBeTransfered = true; } set w(n) { this[3] = n; this.mustBeTransfered = true; } get x() { return this[0]; } get y() { return this[1]; } get z() { return this[2]; } get w() { return this[3]; } } //================================================================================ //-------------- export class Int extends PrimitiveIntUniform { constructor(x = 0, createLocalVariable = false) { super("i32", [x], createLocalVariable); } set x(n) { this[0] = n; this.mustBeTransfered = true; } get x() { return this[0]; } } //-------------- export class IVec2 extends PrimitiveIntUniform { constructor(x = 0, y = 0, createLocalVariable = false) { super("vec2<i32>", [x, y], createLocalVariable); } set x(n) { this[0] = n; this.mustBeTransfered = true; } set y(n) { this[1] = n; this.mustBeTransfered = true; } get x() { return this[0]; } get y() { return this[1]; } } //-------------- export class IVec3 extends PrimitiveIntUniform { constructor(x = 0, y = 0, z = 0, createLocalVariable = false) { super("vec3<i32>", [x, y, z], createLocalVariable); } set x(n) { this[0] = n; this.mustBeTransfered = true; } set y(n) { this[1] = n; this.mustBeTransfered = true; } set z(n) { this[2] = n; this.mustBeTransfered = true; } get x() { return this[0]; } get y() { return this[1]; } get z() { return this[2]; } } //-------------- export class IVec4 extends PrimitiveIntUniform { constructor(x = 0, y = 0, z = 0, w = 0, createLocalVariable = false) { super("vec4<i32>", [x, y, z, w], createLocalVariable); } set x(n) { this[0] = n; this.mustBeTransfered = true; } set y(n) { this[1] = n; this.mustBeTransfered = true; } set z(n) { this[2] = n; this.mustBeTransfered = true; } set w(n) { this[3] = n; this.mustBeTransfered = true; } get x() { return this[0]; } get y() { return this[1]; } get z() { return this[2]; } get w() { return this[3]; } } //================================================================================ //-------------- export class Uint extends PrimitiveUintUniform { constructor(x = 0, createLocalVariable = false) { super("u32", [x], createLocalVariable); } set x(n) { this[0] = n; this.mustBeTransfered = true; } get x() { return this[0]; } } //-------------- export class UVec2 extends PrimitiveUintUniform { constructor(x = 0, y = 0, createLocalVariable = false) { super("vec2<u32>", [x, y], createLocalVariable); } set x(n) { this[0] = n; this.mustBeTransfered = true; } set y(n) { this[1] = n; this.mustBeTransfered = true; } get x() { return this[0]; } get y() { return this[1]; } } //-------------- export class UVec3 extends PrimitiveUintUniform { constructor(x = 0, y = 0, z = 0, createLocalVariable = false) { super("vec3<u32>", [x, y, z], createLocalVariable); } set x(n) { this[0] = n; this.mustBeTransfered = true; } set y(n) { this[1] = n; this.mustBeTransfered = true; } set z(n) { this[2] = n; this.mustBeTransfered = true; } get x() { return this[0]; } get y() { return this[1]; } get z() { return this[2]; } } //-------------- export class UVec4 extends PrimitiveUintUniform { constructor(x = 0, y = 0, z = 0, w = 0, createLocalVariable = false) { super("vec4<u32>", [x, y, z, w], createLocalVariable); } set x(n) { this[0] = n; this.mustBeTransfered = true; } set y(n) { this[1] = n; this.mustBeTransfered = true; } set z(n) { this[2] = n; this.mustBeTransfered = true; } set w(n) { this[3] = n; this.mustBeTransfered = true; } get x() { return this[0]; } get y() { return this[1]; } get z() { return this[2]; } get w() { return this[3]; } } //========================================================================== export class Vec4Array extends PrimitiveFloatUniform { vec4Array; constructor(vec4Array_or_arrayLength) { let array; if (typeof vec4Array_or_arrayLength != "number") array = vec4Array_or_arrayLength; else { array = []; for (let i = 0; i < vec4Array_or_arrayLength; i++) array[i] = new Vec4(); } const len = array.length; let buf = new Float32Array(len * 4); for (let i = 0; i < len; i++) buf.set(array[i], i * 4); let type = "array<vec4<f32>," + array.length + ">"; super("array<vec4<f32>," + array.length + ">", buf); this.className = type; this.vec4Array = array; for (let i = 0; i < this.vec4Array.length; i++) { this.vec4Array[i].addEventListener("ON_CHANGE", () => { this.mustBeTransfered = true; this.set(this.vec4Array[i], i * 4); this.dispatchEvent("ON_CHANGE"); }); } } updateStartIdFromParentToChildren() { //used to update the startId of elements contained in an array //|=> by default, the startId is related to the parent, but the parent may have a parent too so we must update every startId for (let i = 0; i < this.vec4Array.length; i++) { this.vec4Array[i].globalStartId = this.globalStartId + this.vec4Array[i].startId; } } update() { let mustBeTransfered = false; let m; for (let i = 0; i < this.vec4Array.length; i++) { m = this.vec4Array[i]; if (!m.name) m.name = this.name + "#" + i; m.update(); if (m.mustBeTransfered) { mustBeTransfered = true; this.set(m, i * 4); m.mustBeTransfered = false; } } if (this.mustBeTransfered != mustBeTransfered) { this.mustBeTransfered = mustBeTransfered; } } } //------- export class IVec4Array extends PrimitiveIntUniform { ivec4Array; constructor(vec4Array_or_arrayLength) { let array; if (typeof vec4Array_or_arrayLength != "number") array = vec4Array_or_arrayLength; else { array = []; for (let i = 0; i < vec4Array_or_arrayLength; i++) array[i] = new IVec4(); } const len = array.length; let buf = new Int32Array(len * 4); for (let i = 0; i < len; i++) buf.set(array[i], i * 4); let type = "array<vec4<i32>," + array.length + ">"; super(type, buf); this.className = type; this.ivec4Array = array; for (let i = 0; i < this.ivec4Array.length; i++) { this.ivec4Array[i].addEventListener("ON_CHANGE", () => { this.mustBeTransfered = true; this.set(this.ivec4Array[i], i * 4); this.dispatchEvent("ON_CHANGE"); }); } } updateStartIdFromParentToChildren() { //used to update the startId of elements contained in an array //|=> by default, the startId is related to the parent, but the parent may have a parent too so we must update every startId for (let i = 0; i < this.ivec4Array.length; i++) { this.ivec4Array[i].globalStartId = this.globalStartId + this.ivec4Array[i].startId; } } update() { let mustBeTransfered = false; let m; for (let i = 0; i < this.ivec4Array.length; i++) { m = this.ivec4Array[i]; m.update(); if (m.mustBeTransfered) { mustBeTransfered = true; this.set(m, i * 4); m.mustBeTransfered = false; } } this.mustBeTransfered = mustBeTransfered; } } //----- export class UVec4Array extends PrimitiveUintUniform { uvec4Array; constructor(vec4Array_or_arrayLength) { let array; if (typeof vec4Array_or_arrayLength != "number") array = vec4Array_or_arrayLength; else { array = []; for (let i = 0; i < vec4Array_or_arrayLength; i++) array[i] = new UVec4(); } const len = array.length; let buf = new Uint32Array(len * 4); for (let i = 0; i < len; i++) buf.set(array[i], i * 4); let type = "array<vec4<u32>," + array.length + ">"; super(type, buf); this.className = type; this.uvec4Array = array; for (let i = 0; i < this.uvec4Array.length; i++) { this.uvec4Array[i].addEventListener("ON_CHANGE", () => { this.mustBeTransfered = true; this.set(this.uvec4Array[i], i * 4); this.dispatchEvent("ON_CHANGE"); }); } } updateStartIdFromParentToChildren() { //used to update the startId of elements contained in an array //|=> by default, the startId is related to the parent, but the parent may have a parent too so we must update every startId for (let i = 0; i < this.uvec4Array.length; i++) { this.uvec4Array[i].globalStartId = this.globalStartId + this.uvec4Array[i].startId; } } update() { let mustBeTransfered = false; let m; for (let i = 0; i < this.uvec4Array.length; i++) { m = this.uvec4Array[i]; m.update(); if (m.mustBeTransfered) { mustBeTransfered = true; this.set(m, i * 4); m.mustBeTransfered = false; } } this.mustBeTransfered = mustBeTransfered; } } //============================================================== export class Matrix3x3 extends PrimitiveFloatUniform { disableUpdate; constructor(floatArray = null) { const disableUpdate = !!floatArray; if (!floatArray) floatArray = new Float32Array([ 1, 0, 0, 0, 1, 0, 0, 0, 1 ]); super("mat3x3<f32>", floatArray); this.className = "mat3x3<f32>"; this.disableUpdate = disableUpdate; } } /* export class Matrix2x2 extends PrimitiveFloatUniform { constructor() { super("mat2x2<f32>", mat2.create()); } } export class Matrix2x3 extends PrimitiveFloatUniform { constructor() { super("mat2x3<f32>", mat2d.create()); } } */ export class Matrix4x4 extends PrimitiveFloatUniform { _x = 0; _y = 0; _z = 0; _sx = 1; _sy = 1; _sz = 1; _rx = 0; _ry = 0; _rz = 0; disableUpdate; constructor(floatArray = null) { const disableUpdate = !!floatArray; if (!floatArray) floatArray = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); super("mat4x4<f32>", floatArray); this.className = "mat4x4<f32>"; this.disableUpdate = disableUpdate; } clone() { const m = new Matrix4x4(this); m.x = this.x; m.y = this.y; m.z = this.z; m.rotationX = this.rotationX; m.rotationY = this.rotationY; m.rotationZ = this.rotationZ; m.scaleX = this.scaleX; m.scaleY = this.scaleY; m.scaleZ = this.scaleZ; m.disableUpdate = this.disableUpdate; return m; } get x() { return this._x; } get y() { return this._y; } get z() { return this._z; } get rotationX() { return this._rx; } get rotationY() { return this._ry; } get rotationZ() { return this._rz; } get scaleX() { return this._sx; } get scaleY() { return this._sy; } get scaleZ() { return this._sz; } set x(n) { if (n === this._x) return; this.mustBeTransfered = true; this._x = n; } set y(n) { if (n === this._y) return; this.mustBeTransfered = true; this._y = n; } set z(n) { if (n === this._z) return; this.mustBeTransfered = true; this._z = n; } set rotationX(n) { if (n === this._rx) return; this.mustBeTransfered = true; this._rx = n; } set rotationY(n) { if (n === this._ry) return; this.mustBeTransfered = true; this._ry = n; } set rotationZ(n) { if (n === this._rz) return; this.mustBeTransfered = true; this._rz = n; } set scaleX(n) { if (n === this._sx) return; this.mustBeTransfered = true; this._sx = n; } set scaleY(n) { if (n === this._sy) return; this.mustBeTransfered = true; this._sy = n; } set scaleZ(n) { if (n === this._sz) return; this.mustBeTransfered = true; this._sz = n; } set scaleXYZ(n) { this.scaleX = this.scaleY = this.scaleZ = n; this.mustBeTransfered = true; } setMatrix(mat) { this.set(mat); this.mustBeTransfered = true; } update() { //console.log("matrix.update ", this.disableUpdate) if (this.disableUpdate) return; if (this.mustBeTransfered) { mat4.identity(this); mat4.rotate(this, this, this._rx, vec3.fromValues(1, 0, 0)); mat4.rotate(this, this, this._ry, vec3.fromValues(0, 1, 0)); mat4.rotate(this, this, this._rz, vec3.fromValues(0, 0, 1)); mat4.translate(this, this, vec3.fromValues(this._x, this._y, this._z)); mat4.scale(this, this, vec3.fromValues(this._sx, this._sy, this._sz)); /* //with wgpu-matrix mat4.identity(this); mat4.rotateX(this, this._rx, this); mat4.rotateY(this, this._ry, this); mat4.rotateZ(this, this._rz, this); mat4.translate(this, vec3.fromValues(this._x, this._y, this._z), this) mat4.scale(this, vec3.fromValues(this._sx, this._sy, this._sz), this); */ } } } //-------------------- //-------------------- export class Matrix4x4Array extends PrimitiveFloatUniform { matrixs; constructor(mat4x4Array_or_arrayLength) { let array; if (typeof mat4x4Array_or_arrayLength != "number") array = mat4x4Array_or_arrayLength; else { array = []; for (let i = 0; i < mat4x4Array_or_arrayLength; i++) array[i] = new Matrix4x4(); } const len = array.length; let buf = new Float32Array(len * 16); for (let i = 0; i < len; i++) buf.set(array[i], i * 16); super("array<mat4x4<f32>," + len + ">", buf); this.matrixs = array; this.mustBeTransfered = true; this.className = "array<mat4x4<f32>," + len + ">"; for (let i = 0; i < this.matrixs.length; i++) { this.matrixs[i].addEventListener("ON_CHANGE", () => { this.mustBeTransfered = true; this.set(this.matrixs[i], i * 16); this.dispatchEvent("ON_CHANGE"); }); } } updateStartIdFromParentToChildren() { //used to update the startId of elements contained in an array //|=> by default, the startId is related to the parent, but the parent may have a parent too so we must update every startId for (let i = 0; i < this.matrixs.length; i++) { this.matrixs[i].globalStartId = this.globalStartId + this.matrixs[i].startId; } } update() { let mustBeTransfered = false; let m; for (let i = 0; i < this.matrixs.length; i++) { m = this.matrixs[i]; m.update(); if (m.mustBeTransfered) { mustBeTransfered = true; this.set(m, i * 16); m.mustBeTransfered = false; } } this.mustBeTransfered = mustBeTransfered; } }