<commit_before>import { GPUTextureFormat, GPUAddressMode, GPUFilterMode } from './constants.js';
import { Texture, NearestFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearFilter, RepeatWrapping, MirroredRepeatWrapping,
	RGBFormat, RGBAFormat, RGBA_S3TC_DXT1_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT5_Format, UnsignedByteType, FloatType, HalfFloatType
} from '../../../../build/three.module.js';
import WebGPUTextureUtils from './WebGPUTextureUtils.js';

class WebGPUTextures {

	constructor( device, properties, info, glslang ) {

		this.device = device;
		this.properties = properties;
		this.info = info;
		this.glslang = glslang;

		this.defaultTexture = null;
		this.defaultSampler = null;

		this.samplerCache = new Map();
		this.utils = null;

	}

	getDefaultSampler() {

		if ( this.defaultSampler === null ) {

			this.defaultSampler = this.device.createSampler( {} );

		}

		return this.defaultSampler;

	}

	getDefaultTexture() {

		if ( this.defaultTexture === null ) {

			const texture = new Texture();
			texture.minFilter = NearestFilter;
			texture.magFilter = NearestFilter;

			this.defaultTexture = this._createTexture( texture );

		}

		return this.defaultTexture;

	}

	getTextureGPU( texture ) {

		const textureProperties = this.properties.get( texture );

		return textureProperties.textureGPU;

	}

	getSampler( texture ) {

		const textureProperties = this.properties.get( texture );

		return textureProperties.samplerGPU;

	}

	updateTexture( texture ) {

		let forceUpdate = false;

		const textureProperties = this.properties.get( texture );

		if ( texture.version > 0 && textureProperties.version !== texture.version ) {

			const image = texture.image;

			if ( image === undefined ) {

				console.warn( 'THREE.WebGPURenderer: Texture marked for update but image is undefined.' );

			} else if ( image.complete === false ) {

				console.warn( 'THREE.WebGPURenderer: Texture marked for update but image is incomplete.' );

			} else {

				// texture init

				if ( textureProperties.initialized === undefined ) {

					textureProperties.initialized = true;

					const disposeCallback = onTextureDispose.bind( this );
					textureProperties.disposeCallback = disposeCallback;

					texture.addEventListener( 'dispose', disposeCallback );

					this.info.memory.textures ++;

				}

				// texture creation

				if ( textureProperties.textureGPU !== undefined ) {

					// @TODO: Avoid calling of destroy() in certain scenarios. When only the contents of a texture
					// are updated, a buffer upload should be sufficient. However, if the user changes
					// the dimensions of the texture, format or usage, a new instance of GPUTexture is required.

					textureProperties.textureGPU.destroy();

				}

				textureProperties.textureGPU = this._createTexture( texture );
				textureProperties.version = texture.version;
				forceUpdate = true;

			}

		}

		// if the texture is used for RTT, it's necessary to init it once so the binding
		// group's resource definition points to the respective GPUTexture

		if ( textureProperties.initializedRTT === false ) {

			textureProperties.initializedRTT = true;
			forceUpdate = true;

		}

		return forceUpdate;

	}

	updateSampler( texture ) {

		const array = [];

		array.push( texture.wrapS );
		array.push( texture.wrapT );
		array.push( texture.wrapR );
		array.push( texture.magFilter );
		array.push( texture.minFilter );
		array.push( texture.anisotropy );

		const key = array.join();
		let samplerGPU = this.samplerCache.get( key );

		if ( samplerGPU === undefined ) {

			samplerGPU = this.device.createSampler( {
				addressModeU: this._convertAddressMode( texture.wrapS ),
				addressModeV: this._convertAddressMode( texture.wrapT ),
				addressModeW: this._convertAddressMode( texture.wrapR ),
				magFilter: this._convertFilterMode( texture.magFilter ),
				minFilter: this._convertFilterMode( texture.minFilter ),
				mipmapFilter: this._convertFilterMode( texture.minFilter ),
				maxAnisotropy: texture.anisotropy
			} );

			this.samplerCache.set( key, samplerGPU );

		}

		const textureProperties = this.properties.get( texture );
		textureProperties.samplerGPU = samplerGPU;

	}

	initRenderTarget( renderTarget ) {

		const properties = this.properties;
		const renderTargetProperties = properties.get( renderTarget );

		if ( renderTargetProperties.initialized === undefined ) {

			const device = this.device;

			const width = renderTarget.width;
			const height = renderTarget.height;

			const colorTextureGPU = device.createTexture( {
				size: {
					width: width,
					height: height,
					depth: 1
				},
				format: GPUTextureFormat.BRGA8Unorm, // @TODO: Make configurable
				usage: GPUTextureUsage.OUTPUT_ATTACHMENT | GPUTextureUsage.SAMPLED
			} );

			renderTargetProperties.colorTextureGPU = colorTextureGPU;

			// When the ".texture" or ".depthTexture" property of a render target is used as a map,
			// the renderer has to find the respective GPUTexture objects to setup the bind groups.
			// Since it's not possible to see just from a texture object whether it belongs to a render
			// target or not, we need the initializedRTT flag.

			const textureProperties = properties.get( renderTarget.texture );
			textureProperties.textureGPU = colorTextureGPU;
			textureProperties.initializedRTT = false;

			if ( renderTarget.depthBuffer === true ) {

				const depthTextureGPU = device.createTexture( {
					size: {
						width: width,
						height: height,
						depth: 1
					},
					format: GPUTextureFormat.Depth24PlusStencil8, // @TODO: Make configurable
					usage: GPUTextureUsage.OUTPUT_ATTACHMENT
				} );

				renderTargetProperties.depthTextureGPU = depthTextureGPU;

				if ( renderTarget.depthTexture !== null ) {

					const depthTextureProperties = properties.get( renderTarget.depthTexture );
					depthTextureProperties.textureGPU = depthTextureGPU;
					depthTextureProperties.initializedRTT = false;

				}

			}

			//

			const disposeCallback = onRenderTargetDispose.bind( this );
			renderTargetProperties.disposeCallback = disposeCallback;

			renderTarget.addEventListener( 'dispose', disposeCallback );

			//

			renderTargetProperties.initialized = true;

		}

	}

	dispose() {

		this.samplerCache.clear();

	}

	_convertAddressMode( value ) {

		let addressMode = GPUAddressMode.ClampToEdge;

		if ( value === RepeatWrapping ) {

			addressMode = GPUAddressMode.Repeat;

		} else if ( value === MirroredRepeatWrapping ) {

			addressMode = GPUAddressMode.MirrorRepeat;

		}

		return addressMode;

	}

	_convertFilterMode( value ) {

		let filterMode = GPUFilterMode.Linear;

		if ( value === NearestFilter || value === NearestMipmapNearestFilter || value === NearestMipmapLinearFilter ) {

			filterMode = GPUFilterMode.Nearest;

		}

		return filterMode;

	}

	_createTexture( texture ) {

		const device = this.device;
		const image = texture.image;

		const width = ( image !== undefined ) ? image.width : 1;
		const height = ( image !== undefined ) ? image.height : 1;

		const format = this._getFormat( texture );
		const needsMipmaps = this._needsMipmaps( texture );

		let mipLevelCount;
		let usage = GPUTextureUsage.SAMPLED | GPUTextureUsage.COPY_DST;

		if ( texture.isCompressedTexture ) {

			mipLevelCount = texture.mipmaps.length;

		} else {

			if ( needsMipmaps === true ) {

				mipLevelCount = this._getMipLevelCount( width, height );

				// current mipmap generation requires OUTPUT_ATTACHMENT

				usage |= GPUTextureUsage.OUTPUT_ATTACHMENT;

			}

		}

		// texture creation

		const textureGPUDescriptor = {
			size: {
				width: width,
				height: height,
				depth: 1,
			},
			format: format,
			usage: usage,
			mipLevelCount: mipLevelCount
		};
		const textureGPU = device.createTexture( textureGPUDescriptor );

		// transfer texture data

		if ( texture.isDataTexture ) {

			this._copyBufferToTexture( image, format, textureGPU );

		} else if ( texture.isCompressedTexture ) {

			this._copyCompressedTextureDataToTexture( texture.mipmaps, format, textureGPU );

		} else {

			// convert HTML iamges and canvas elements to ImageBitmap before copy

			if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) ||
				( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ) {

				const options = {};

				options.imageOrientation = ( texture.flipY === true ) ? 'flipY' : 'none';
				options.premultiplyAlpha = ( texture.premultiplyAlpha === true ) ? 'premultiply' : 'default';

				createImageBitmap( image, 0, 0, width, height, options ).then( imageBitmap => {

					this._copyImageBitmapToTexture( imageBitmap, textureGPU, needsMipmaps, textureGPUDescriptor );

				} );

			} else {

				if ( image !== undefined ) {

					// assuming ImageBitmap. Directly start copy operation of the contents of ImageBitmap into the destination texture

					this._copyImageBitmapToTexture( image, textureGPU, needsMipmaps, textureGPUDescriptor );

				}

			}

		}

		return textureGPU;

	}

	_copyBufferToTexture( image, format, textureGPU ) {

		// @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture()
		// @TODO: Consider to support valid buffer layouts with other formats like RGB

		const data = image.data;

		const bytesPerTexel = this._getBytesPerTexel( format );
		const bytesPerRow = Math.ceil( image.width * bytesPerTexel / 256 ) * 256;

		this.device.defaultQueue.writeTexture(
			{
				texture: textureGPU,
				mipLevel: 0 // @TODO: Support mipmaps
			},
			data,
			{
				offset: 0,
				bytesPerRow
			},
			{
				width: image.width,
				height: image.height,
				depth: 1
			} );

	}

	_copyImageBitmapToTexture( imageBitmap, textureGPU, needsMipmaps, textureGPUDescriptor ) {

		const device = this.device;

		device.defaultQueue.copyImageBitmapToTexture(
			{
				imageBitmap: imageBitmap
			}, {
				texture: textureGPU
			}, {
				width: imageBitmap.width,
				height: imageBitmap.height,
				depth: 1
			}
		);

		if ( needsMipmaps === true ) {

			if ( this.utils === null ) {

				this.utils = new WebGPUTextureUtils( this.device, this.glslang ); // only create this helper if necessary

			}

			this.utils.generateMipmaps( imageBitmap, textureGPU, textureGPUDescriptor );

		}

	}

	_copyCompressedTextureDataToTexture( mipmaps, format, textureGPU ) {

		// @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture()

		const blockData = this._getBlockData( format );

		for ( let i = 0; i < mipmaps.length; i ++ ) {

			const mipmap = mipmaps[ i ];

			const width = mipmap.width;
			const height = mipmap.height;

			const bytesPerRow = Math.ceil( width / blockData.width ) * blockData.byteLength;

			this.device.defaultQueue.writeTexture(
				{
					texture: textureGPU,
					mipLevel: i
				},
				mipmap.data,
				{
					offset: 0,
					bytesPerRow
				},
				{
					width: Math.ceil( width / blockData.width ) * blockData.width,
					height: Math.ceil( height / blockData.width ) * blockData.width,
					depth: 1,
				} );

		}

	}

	_getBlockData( format ) {

		if ( format === GPUTextureFormat.BC1RGBAUnorm ) return { byteLength: 8, width: 4, height: 4 };
		if ( format === GPUTextureFormat.BC2RGBAUnorm ) return { byteLength: 16, width: 4, height: 4 };
		if ( format === GPUTextureFormat.BC3RGBAUnorm ) return { byteLength: 16, width: 4, height: 4 };

	}

	_getBytesPerTexel( format ) {

		if ( format === GPUTextureFormat.RGBA8Unorm ) return 4;
		if ( format === GPUTextureFormat.RGBA16Float ) return 8;
		if ( format === GPUTextureFormat.RGBA32Float ) return 16;

	}

	_getFormat( texture ) {

		const format = texture.format;
		const type = texture.type;

		let formatGPU;

		switch ( format ) {

			case RGBA_S3TC_DXT1_Format:
				formatGPU = GPUTextureFormat.BC1RGBAUnorm;
				break;

			case RGBA_S3TC_DXT3_Format:
				formatGPU = GPUTextureFormat.BC2RGBAUnorm;
				break;

			case RGBA_S3TC_DXT5_Format:
				formatGPU = GPUTextureFormat.BC3RGBAUnorm;
				break;

			case RGBFormat:
			case RGBAFormat:

				switch ( type ) {

					case UnsignedByteType:
						formatGPU = GPUTextureFormat.RGBA8Unorm;
						break;

					case FloatType:
						formatGPU = GPUTextureFormat.RGBA32Float;
						break;

					case HalfFloatType:
						formatGPU = GPUTextureFormat.RGBA16Float;
						break;

					default:
						console.error( 'WebGPURenderer: Unsupported texture type with RGBAFormat.', type );

				}

				break;

			default:
				console.error( 'WebGPURenderer: Unsupported texture format.', format );

		}

		return formatGPU;

	}

	_getMipLevelCount( width, height ) {

		return Math.floor( Math.log2( Math.max( width, height ) ) ) + 1;

	}

	_needsMipmaps( texture ) {

		return ( texture.isCompressedTexture !== true ) && ( texture.generateMipmaps === true ) && ( texture.minFilter !== NearestFilter ) && ( texture.minFilter !== LinearFilter );

	}

}

function onRenderTargetDispose( event ) {

	const renderTarget = event.target;
	const properties = this.properties;

	const renderTargetProperties = properties.get( renderTarget );

	renderTarget.removeEventListener( 'dispose', renderTargetProperties.disposeCallback );

	renderTargetProperties.colorTextureGPU.destroy();
	properties.remove( renderTarget.texture );

	if ( renderTarget.depthBuffer === true ) {

		renderTargetProperties.depthTextureGPU.destroy();

		if ( renderTarget.depthTexture !== null ) {

			properties.remove( renderTarget.depthTexture );

		}

	}

	properties.remove( renderTarget );

}

function onTextureDispose( event ) {

	const texture = event.target;

	const textureProperties = this.properties.get( texture );
	textureProperties.textureGPU.destroy();

	texture.removeEventListener( 'dispose', textureProperties.disposeCallback );

	this.properties.remove( texture );

	this.info.memory.textures --;

}

export default WebGPUTextures;
<commit_msg>WebGPUTextures: Clean up.<commit_after>import { GPUTextureFormat, GPUAddressMode, GPUFilterMode } from './constants.js';
import { Texture, NearestFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearFilter, RepeatWrapping, MirroredRepeatWrapping,
	RGBFormat, RGBAFormat, RGBA_S3TC_DXT1_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT5_Format, UnsignedByteType, FloatType, HalfFloatType
} from '../../../../build/three.module.js';
import WebGPUTextureUtils from './WebGPUTextureUtils.js';

class WebGPUTextures {

	constructor( device, properties, info, glslang ) {

		this.device = device;
		this.properties = properties;
		this.info = info;
		this.glslang = glslang;

		this.defaultTexture = null;
		this.defaultSampler = null;

		this.samplerCache = new Map();
		this.utils = null;

	}

	getDefaultSampler() {

		if ( this.defaultSampler === null ) {

			this.defaultSampler = this.device.createSampler( {} );

		}

		return this.defaultSampler;

	}

	getDefaultTexture() {

		if ( this.defaultTexture === null ) {

			const texture = new Texture();
			texture.minFilter = NearestFilter;
			texture.magFilter = NearestFilter;

			this.defaultTexture = this._createTexture( texture );

		}

		return this.defaultTexture;

	}

	getTextureGPU( texture ) {

		const textureProperties = this.properties.get( texture );

		return textureProperties.textureGPU;

	}

	getSampler( texture ) {

		const textureProperties = this.properties.get( texture );

		return textureProperties.samplerGPU;

	}

	updateTexture( texture ) {

		let forceUpdate = false;

		const textureProperties = this.properties.get( texture );

		if ( texture.version > 0 && textureProperties.version !== texture.version ) {

			const image = texture.image;

			if ( image === undefined ) {

				console.warn( 'THREE.WebGPURenderer: Texture marked for update but image is undefined.' );

			} else if ( image.complete === false ) {

				console.warn( 'THREE.WebGPURenderer: Texture marked for update but image is incomplete.' );

			} else {

				// texture init

				if ( textureProperties.initialized === undefined ) {

					textureProperties.initialized = true;

					const disposeCallback = onTextureDispose.bind( this );
					textureProperties.disposeCallback = disposeCallback;

					texture.addEventListener( 'dispose', disposeCallback );

					this.info.memory.textures ++;

				}

				// texture creation

				if ( textureProperties.textureGPU !== undefined ) {

					// @TODO: Avoid calling of destroy() in certain scenarios. When only the contents of a texture
					// are updated, a buffer upload should be sufficient. However, if the user changes
					// the dimensions of the texture, format or usage, a new instance of GPUTexture is required.

					textureProperties.textureGPU.destroy();

				}

				textureProperties.textureGPU = this._createTexture( texture );
				textureProperties.version = texture.version;
				forceUpdate = true;

			}

		}

		// if the texture is used for RTT, it's necessary to init it once so the binding
		// group's resource definition points to the respective GPUTexture

		if ( textureProperties.initializedRTT === false ) {

			textureProperties.initializedRTT = true;
			forceUpdate = true;

		}

		return forceUpdate;

	}

	updateSampler( texture ) {

		const array = [];

		array.push( texture.wrapS );
		array.push( texture.wrapT );
		array.push( texture.wrapR );
		array.push( texture.magFilter );
		array.push( texture.minFilter );
		array.push( texture.anisotropy );

		const key = array.join();
		let samplerGPU = this.samplerCache.get( key );

		if ( samplerGPU === undefined ) {

			samplerGPU = this.device.createSampler( {
				addressModeU: this._convertAddressMode( texture.wrapS ),
				addressModeV: this._convertAddressMode( texture.wrapT ),
				addressModeW: this._convertAddressMode( texture.wrapR ),
				magFilter: this._convertFilterMode( texture.magFilter ),
				minFilter: this._convertFilterMode( texture.minFilter ),
				mipmapFilter: this._convertFilterMode( texture.minFilter ),
				maxAnisotropy: texture.anisotropy
			} );

			this.samplerCache.set( key, samplerGPU );

		}

		const textureProperties = this.properties.get( texture );
		textureProperties.samplerGPU = samplerGPU;

	}

	initRenderTarget( renderTarget ) {

		const properties = this.properties;
		const renderTargetProperties = properties.get( renderTarget );

		if ( renderTargetProperties.initialized === undefined ) {

			const device = this.device;

			const width = renderTarget.width;
			const height = renderTarget.height;

			const colorTextureGPU = device.createTexture( {
				size: {
					width: width,
					height: height,
					depth: 1
				},
				format: GPUTextureFormat.BRGA8Unorm, // @TODO: Make configurable
				usage: GPUTextureUsage.OUTPUT_ATTACHMENT | GPUTextureUsage.SAMPLED
			} );

			renderTargetProperties.colorTextureGPU = colorTextureGPU;

			// When the ".texture" or ".depthTexture" property of a render target is used as a map,
			// the renderer has to find the respective GPUTexture objects to setup the bind groups.
			// Since it's not possible to see just from a texture object whether it belongs to a render
			// target or not, we need the initializedRTT flag.

			const textureProperties = properties.get( renderTarget.texture );
			textureProperties.textureGPU = colorTextureGPU;
			textureProperties.initializedRTT = false;

			if ( renderTarget.depthBuffer === true ) {

				const depthTextureGPU = device.createTexture( {
					size: {
						width: width,
						height: height,
						depth: 1
					},
					format: GPUTextureFormat.Depth24PlusStencil8, // @TODO: Make configurable
					usage: GPUTextureUsage.OUTPUT_ATTACHMENT
				} );

				renderTargetProperties.depthTextureGPU = depthTextureGPU;

				if ( renderTarget.depthTexture !== null ) {

					const depthTextureProperties = properties.get( renderTarget.depthTexture );
					depthTextureProperties.textureGPU = depthTextureGPU;
					depthTextureProperties.initializedRTT = false;

				}

			}

			//

			const disposeCallback = onRenderTargetDispose.bind( this );
			renderTargetProperties.disposeCallback = disposeCallback;

			renderTarget.addEventListener( 'dispose', disposeCallback );

			//

			renderTargetProperties.initialized = true;

		}

	}

	dispose() {

		this.samplerCache.clear();

	}

	_convertAddressMode( value ) {

		let addressMode = GPUAddressMode.ClampToEdge;

		if ( value === RepeatWrapping ) {

			addressMode = GPUAddressMode.Repeat;

		} else if ( value === MirroredRepeatWrapping ) {

			addressMode = GPUAddressMode.MirrorRepeat;

		}

		return addressMode;

	}

	_convertFilterMode( value ) {

		let filterMode = GPUFilterMode.Linear;

		if ( value === NearestFilter || value === NearestMipmapNearestFilter || value === NearestMipmapLinearFilter ) {

			filterMode = GPUFilterMode.Nearest;

		}

		return filterMode;

	}

	_createTexture( texture ) {

		const device = this.device;
		const image = texture.image;

		const width = ( image !== undefined ) ? image.width : 1;
		const height = ( image !== undefined ) ? image.height : 1;

		const format = this._getFormat( texture );
		const needsMipmaps = this._needsMipmaps( texture );

		let mipLevelCount;
		let usage = GPUTextureUsage.SAMPLED | GPUTextureUsage.COPY_DST;

		if ( texture.isCompressedTexture ) {

			mipLevelCount = texture.mipmaps.length;

		} else {

			if ( needsMipmaps === true ) {

				mipLevelCount = this._getMipLevelCount( width, height );

				// current mipmap generation requires OUTPUT_ATTACHMENT

				usage |= GPUTextureUsage.OUTPUT_ATTACHMENT;

			}

		}

		// texture creation

		const textureGPUDescriptor = {
			size: {
				width: width,
				height: height,
				depth: 1,
			},
			format: format,
			usage: usage,
			mipLevelCount: mipLevelCount
		};
		const textureGPU = device.createTexture( textureGPUDescriptor );

		// transfer texture data

		if ( texture.isDataTexture ) {

			this._copyBufferToTexture( image, format, textureGPU );

		} else if ( texture.isCompressedTexture ) {

			this._copyCompressedTextureDataToTexture( texture.mipmaps, format, textureGPU );

		} else {

			// convert HTML images and canvas elements to ImageBitmap

			if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) ||
				( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ) {

				const options = {};

				options.imageOrientation = ( texture.flipY === true ) ? 'flipY' : 'none';
				options.premultiplyAlpha = ( texture.premultiplyAlpha === true ) ? 'premultiply' : 'default';

				createImageBitmap( image, 0, 0, width, height, options ).then( imageBitmap => {

					this._copyImageBitmapToTexture( imageBitmap, textureGPU, needsMipmaps, textureGPUDescriptor );

				} );

			} else {

				if ( image !== undefined ) {

					// assume ImageBitmap

					this._copyImageBitmapToTexture( image, textureGPU, needsMipmaps, textureGPUDescriptor );

				}

			}

		}

		return textureGPU;

	}

	_copyBufferToTexture( image, format, textureGPU ) {

		// @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture()
		// @TODO: Consider to support valid buffer layouts with other formats like RGB

		const data = image.data;

		const bytesPerTexel = this._getBytesPerTexel( format );
		const bytesPerRow = Math.ceil( image.width * bytesPerTexel / 256 ) * 256;

		this.device.defaultQueue.writeTexture(
			{
				texture: textureGPU,
				mipLevel: 0 // @TODO: Support mipmaps
			},
			data,
			{
				offset: 0,
				bytesPerRow
			},
			{
				width: image.width,
				height: image.height,
				depth: 1
			} );

	}

	_copyImageBitmapToTexture( imageBitmap, textureGPU, needsMipmaps, textureGPUDescriptor ) {

		const device = this.device;

		device.defaultQueue.copyImageBitmapToTexture(
			{
				imageBitmap: imageBitmap
			}, {
				texture: textureGPU
			}, {
				width: imageBitmap.width,
				height: imageBitmap.height,
				depth: 1
			}
		);

		if ( needsMipmaps === true ) {

			if ( this.utils === null ) {

				this.utils = new WebGPUTextureUtils( this.device, this.glslang ); // only create this helper if necessary

			}

			this.utils.generateMipmaps( imageBitmap, textureGPU, textureGPUDescriptor );

		}

	}

	_copyCompressedTextureDataToTexture( mipmaps, format, textureGPU ) {

		// @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture()

		const blockData = this._getBlockData( format );

		for ( let i = 0; i < mipmaps.length; i ++ ) {

			const mipmap = mipmaps[ i ];

			const width = mipmap.width;
			const height = mipmap.height;

			const bytesPerRow = Math.ceil( width / blockData.width ) * blockData.byteLength;

			this.device.defaultQueue.writeTexture(
				{
					texture: textureGPU,
					mipLevel: i
				},
				mipmap.data,
				{
					offset: 0,
					bytesPerRow
				},
				{
					width: Math.ceil( width / blockData.width ) * blockData.width,
					height: Math.ceil( height / blockData.width ) * blockData.width,
					depth: 1,
				} );

		}

	}

	_getBlockData( format ) {

		if ( format === GPUTextureFormat.BC1RGBAUnorm ) return { byteLength: 8, width: 4, height: 4 };
		if ( format === GPUTextureFormat.BC2RGBAUnorm ) return { byteLength: 16, width: 4, height: 4 };
		if ( format === GPUTextureFormat.BC3RGBAUnorm ) return { byteLength: 16, width: 4, height: 4 };

	}

	_getBytesPerTexel( format ) {

		if ( format === GPUTextureFormat.RGBA8Unorm ) return 4;
		if ( format === GPUTextureFormat.RGBA16Float ) return 8;
		if ( format === GPUTextureFormat.RGBA32Float ) return 16;

	}

	_getFormat( texture ) {

		const format = texture.format;
		const type = texture.type;

		let formatGPU;

		switch ( format ) {

			case RGBA_S3TC_DXT1_Format:
				formatGPU = GPUTextureFormat.BC1RGBAUnorm;
				break;

			case RGBA_S3TC_DXT3_Format:
				formatGPU = GPUTextureFormat.BC2RGBAUnorm;
				break;

			case RGBA_S3TC_DXT5_Format:
				formatGPU = GPUTextureFormat.BC3RGBAUnorm;
				break;

			case RGBFormat:
			case RGBAFormat:

				switch ( type ) {

					case UnsignedByteType:
						formatGPU = GPUTextureFormat.RGBA8Unorm;
						break;

					case FloatType:
						formatGPU = GPUTextureFormat.RGBA32Float;
						break;

					case HalfFloatType:
						formatGPU = GPUTextureFormat.RGBA16Float;
						break;

					default:
						console.error( 'WebGPURenderer: Unsupported texture type with RGBAFormat.', type );

				}

				break;

			default:
				console.error( 'WebGPURenderer: Unsupported texture format.', format );

		}

		return formatGPU;

	}

	_getMipLevelCount( width, height ) {

		return Math.floor( Math.log2( Math.max( width, height ) ) ) + 1;

	}

	_needsMipmaps( texture ) {

		return ( texture.isCompressedTexture !== true ) && ( texture.generateMipmaps === true ) && ( texture.minFilter !== NearestFilter ) && ( texture.minFilter !== LinearFilter );

	}

}

function onRenderTargetDispose( event ) {

	const renderTarget = event.target;
	const properties = this.properties;

	const renderTargetProperties = properties.get( renderTarget );

	renderTarget.removeEventListener( 'dispose', renderTargetProperties.disposeCallback );

	renderTargetProperties.colorTextureGPU.destroy();
	properties.remove( renderTarget.texture );

	if ( renderTarget.depthBuffer === true ) {

		renderTargetProperties.depthTextureGPU.destroy();

		if ( renderTarget.depthTexture !== null ) {

			properties.remove( renderTarget.depthTexture );

		}

	}

	properties.remove( renderTarget );

}

function onTextureDispose( event ) {

	const texture = event.target;

	const textureProperties = this.properties.get( texture );
	textureProperties.textureGPU.destroy();

	texture.removeEventListener( 'dispose', textureProperties.disposeCallback );

	this.properties.remove( texture );

	this.info.memory.textures --;

}

export default WebGPUTextures;
<|endoftext|>


<commit_before>function initTask(subTask) {
   var state = {};
   var level;
   var answer = null;

   var defaultVisualGraphJSON = {
      "vertexVisualInfo": {
         "v_0":{"x":64,"y":92},
         "v_1":{"x":169,"y":100},
         "v_2":{"x":212,"y":197},
         "v_3":{"x":113,"y":290} },
      "edgeVisualInfo": {
         "e_0":{},
         "e_1":{},
         "e_2":{},
         "e_3":{} },
      "minGraph": {
         "vertexInfo": {
            "v_0":{label:"v_0"},
            "v_1":{label:"v_1"},
            "v_2":{label:"v_2"},
            "v_3":{label:"v_3"} },
         "edgeInfo": {
            "e_0":{label:"e_0"},
            "e_1":{label:"e_1"},
            "e_2":{label:"e_2"},
            "e_3":{label:"e_3"} },
         "edgeVertices": {
            "e_0":["v_0","v_3"],
            "e_1":["v_3","v_2"],
            "e_2":["v_2","v_1"],
            "e_3":["v_2","v_0"]},
         "directed":true }
      };
   var defaultCircleAttr = {
      "r": 20,
      "fill": "#AA0000",
      "stroke": "black",
      "stroke-width": 1
   };
   var defaultLineAttr = {
      "stroke": "black",
      "stroke-width": 4,
      "arrow-end": "long-classic-wide"
   };

   var paperWidth = 750;
   var paperHeight = 500;

   var paper;
   var graph;
   var visualGraph;
   var graphMouse;
   var graphDrawer;
   var graphEditor;
   var snapToGrid;
   var dragOverlay;
   var dragInitialPositions;
   var dragInitialMouse;
   var keepProportions;

   var showGrid = true;
   var gridSize = 32;
   var grid;

   var vertexGuid = 0;
   var edgeGuid = 0;

   var circleAttr;
   var lineAttr;

   var selectedCircleAttr = {
      stroke: "blue",
      "stroke-width": 6
   };

   var gridLineAttr = {
      stroke: "black",
      opacity: 0.5,
      "stroke-width": 1
   };

   var vertexTextAttr = {
      "font-size": 16,
      "fill": "white"
   };

   subTask.loadLevel = function(curLevel) {
      level = curLevel;
      displayHelper.hideValidateButton = true;
   };

   subTask.getStateObject = function() {
      return state;
   };

   subTask.reloadAnswerObject = function(answerObj) {
      answer = answerObj;
   };

   subTask.resetDisplay = function() {
      initJSON();
      initPaper();
      initHandlers();
      applyAttr();
      fromJSON();
      updateJSON(false);
      updateGridVisibility(showGrid);
      updateInfo();
   };

   subTask.getAnswerObject = function() {
      answer.visual_json[answer.visual_json.length - 1] = $("#visual_json").val();
      answer.circleAttr = $("#circleAttr").val();
      answer.lineAttr = $("#lineAttr").val();
      return answer;
   };

   subTask.getDefaultAnswerObject = function() {
      return {
         visual_json: [JSON.stringify(defaultVisualGraphJSON)],
         circleAttr: JSON.stringify(defaultCircleAttr),
         lineAttr: JSON.stringify(defaultLineAttr)
      };
   };

   subTask.unloadLevel = function(callback) {
      if(visualGraph) {
         disableAllMouse();
      }
      callback();
   };

   var initJSON = function() {
      $("#visual_json").val(answer.visual_json[answer.visual_json.length - 1]);
      $("#circleAttr").val(JSON.stringify(JSON.parse(answer.circleAttr), null, 2));
      $("#lineAttr").val(JSON.stringify(JSON.parse(answer.lineAttr), null, 2));
   };

   var initHandlers = function() {
      $("#pretty").unbind('change');
      $("#pretty").change(updateJSON);
      $("#import").unbind('click');
      $("#import").click(fromJSON);
      $("#directed").unbind('change');
      $("#directed").change(function() {
         updateDirected(this.checked);
      });
      $("#createVertex").off("change");
      $("#createVertex").change(function(){
         graphEditor.vertexCreator.setEnabled(this.checked);
         graphEditor.removeVertexEnabled = this.checked;
         updateOptionsJSON();
      });
      $("#dragVertex").off("change");
      $("#dragVertex").change(function(){
         graphEditor.setVertexDragEnabled(this.checked);
         updateOptionsJSON();
      });
      $("#createEdge").off("change");
      $("#createEdge").change(function(){
         graphEditor.createEdgeEnabled = this.checked;
         graphEditor.removeEdgeEnabled = this.checked;
         updateOptionsJSON();
      });
      $("#dragEdge").off("change");
      $("#dragEdge").change(function(){
         graphEditor.arcDragger.setEnabled(this.checked);
         updateOptionsJSON();
      });
      $("#multipleEdges").off("change");
      $("#multipleEdges").change(function(){
         graphEditor.multipleEdgesEnabled = this.checked;
         updateOptionsJSON();
      });
      $("#loop").off("change");
      $("#loop").change(function(){
         graphEditor.loopEnabled = this.checked;
         updateOptionsJSON();
      });
      $("#editVertexLabel").off("change");
      $("#editVertexLabel").change(function(){
         graphEditor.editVertexLabelEnabled = this.checked;
         updateOptionsJSON();
      });
      $("#editEdgeLabel").off("change");
      $("#editEdgeLabel").change(function(){
         graphEditor.editEdgeLabelEnabled = this.checked;
         updateOptionsJSON();
      });
      $("#dragGraph").off("change");
      $("#dragGraph").change(function(){
         graphEditor.graphDragger.dragEnabled = this.checked;
         updateOptionsJSON();
      });
      $("#scaleGraph").off("change");
      $("#scaleGraph").change(function(){
         graphEditor.graphDragger.scaleEnabled = this.checked;
         updateOptionsJSON();
      });
      $("#snapToGrid").change(function() {
         snapToGrid = this.checked;
         graphEditor.setGridEnabled(snapToGrid, gridSize, gridSize);
         updateOptionsJSON();
      });
      $("#useGrid").unbind('change');
      $("#useGrid").change(function() {
         updateGridVisibility(this.checked);
         updateOptionsJSON();
      });
      $("#gridSize").unbind('change');
      $("#gridSize").change(function() {
         updateGridSize(this.value);
         updateOptionsJSON();
      });
      $("#apply_attr").unbind('click');
      $("#apply_attr").click(applyAttr);
      $("#undo").unbind('click');
      $("#undo").click(clickUndo);
      $("#undo").attr("disabled", answer.visual_json.length == 1);
   };

   var clickUndo = function() {
      if(answer.visual_json.length == 1) {
         return;
      }
      answer.visual_json.pop();
      $("#visual_json").val(answer.visual_json[answer.visual_json.length - 1]);
      fromJSON();
      if(answer.visual_json.length == 1) {
         $("#undo").attr("disabled", true);
      }
   };

   var applyAttr = function() {
      circleAttr = JSON.parse($("#circleAttr").val());
      lineAttr = JSON.parse($("#lineAttr").val());
      if(visualGraph) {
         graphDrawer.setCircleAttr(circleAttr);
         graphDrawer.setLineAttr(lineAttr);
         visualGraph.redraw();
         disableAllMouse();
         graphEditor.setEnabled(true);
      }
   };

   var updateDirected = function(directed) {
      graph.directed = directed;
      updateJSON();
   };

   var updateGridVisibility = function(show) {
      showGrid = show;
      removeGrid();
      if(showGrid) {
         drawGrid();
      }
   };

   var updateGridSize = function(size) {
      size = parseInt(size);
      if (isNaN(size) || size <= 0) {
        return;
      }
      gridSize = size;
      updateGridVisibility(showGrid);

      if(snapToGrid) {
         graphEditor.setGridEnabled(true, gridSize, gridSize);
      }
      // TODO: keep track of the value in JSON?
   };

   var removeGrid = function() {
      if(grid) {
         grid.remove();
         grid = null;
      }
   };

   var drawGrid = function() {
      removeGrid();
      if(!showGrid) {
         return;
      }

      var rows = Math.ceil(paperHeight / gridSize);
      var cols = Math.ceil(paperWidth / gridSize);

      grid = new Grid("anim", paper, rows, cols, gridSize, gridSize, 0, 0, gridLineAttr);
      grid.linesToBack();
   };

   var disableAllMouse = function() {
      if(!graphEditor){
         return;
      }
      graphEditor.setEnabled(false);
   };

   var initPaper = function() {
      paper = subTask.raphaelFactory.create("anim", "anim", paperWidth, paperHeight);
      paper.rect(0, 0, paperWidth, paperHeight).attr("stroke-width", 2);
      dragOverlay = paper.rect(0, 0, paperWidth, paperHeight).attr({
         fill: "green",
         opacity: 0
      }).hide();
   };

   var onVisualGraphChange = function() {
      updateJSON(true);
      updateInfo();
   };

   var updateInfo = function() {
      var size = getVisualSize();
      var html = "Size: " + size.width + " x " + size.height + "<br>";
      var margins = getVisualMargins();
      html += "With symmetric margins: " + (size.width + margins.x) + " x " + (size.height + margins.y);
      $("#info").html(html);
   };

   var getVisualSize = function() {
      var maxX = 0;
      var maxY = 0;
      graph.forEachVertex(function (id) {
         var position = graphDrawer.getVertexPosition(id);
         maxX = Math.max(maxX, position.x + circleAttr.r);
         maxY = Math.max(maxY, position.y + circleAttr.r);
      });
      return {
         width: Math.ceil(maxX),
         height: Math.ceil(maxY)
      };
   };

   var getVisualMargins = function() {
      var minX = paperWidth;
      var minY = paperHeight;
      graph.forEachVertex(function (id) {
         var position = graphDrawer.getVertexPosition(id);
         minX = Math.min(minX, position.x - circleAttr.r);
         minY = Math.min(minY, position.y - circleAttr.r);
      });
      return {
         x: Math.floor(minX),
         y: Math.floor(minY)
      };
   };

   var fromJSON = function() {
      if(visualGraph) {
         disableAllMouse();
         visualGraph.remove();
      }
      graphDrawer = new SimpleGraphDrawer(circleAttr, lineAttr, null, true);
      graphDrawer.setVertexLabelAttr(vertexTextAttr);
      visualGraph = VisualGraph.fromJSON($("#visual_json").val(), "visual", paper, null, graphDrawer, true);
      graph = visualGraph.graph;
      graphMouse = new GraphMouse("mouse", graph, visualGraph);
      $("#directed").prop('checked', graph.directed);
      initVisuals();
   };

   var initVisuals = function() {
      var settings = {
         id: "graphEditor",
         paper: paper,
         graph: graph,
         paperElementID: "anim",
         visualGraph: visualGraph,
         graphMouse: graphMouse,
         dragThreshold: 10,
         edgeThreshold: 20,
         dragLimits: {
            minX: circleAttr.r,
            maxX: paperWidth - circleAttr.r,
            minY: circleAttr.r,
            maxY: paperHeight - circleAttr.r
         },
         vertexLabelAttr: vertexTextAttr,
         onDragEnd: onVisualGraphChange,
         callback: onVisualGraphChange,
         enabled: true
      };
      graphEditor = new GraphEditor(settings);
   };

   var updateJSON = function(updateAnswer) {
      updateOptionsJSON();
      var visualJSON = visualGraph.toJSON();
      if($("#pretty").is(":checked")) {
         visualJSON = JSON.stringify(JSON.parse(visualJSON), null, 2);
      }
      $("#visual_json").val(visualJSON);
      if(updateAnswer) {
         answer.visual_json.push(visualJSON);
         $("#undo").attr("disabled", false);
      }
   };

   var updateOptionsJSON = function() {
      var optionsJSON = optionsToJSON();
      if($("#pretty").is(":checked")) {
         optionsJSON = JSON.stringify(JSON.parse(optionsJSON), null, 2);
      }
      $("#options_json").val(optionsJSON);
   };

   function optionsToJSON() {
      var obj = {
         createVertex:$("#createVertex").is(":checked"),
         createEdge:$("#createEdge").is(":checked"),
         dragVertex:$("#dragVertex").is(":checked"),
         dragEdge:$("#dragEdge").is(":checked"),
         dragGraph:$("#dragGraph").is(":checked"),
         scaleGraph:$("#scaleGraph").is(":checked"),
         multipleEdges:$("#multipleEdges").is(":checked"),
         loop:$("#loop").is(":checked"),
         editVertexLabel:$("#editVertexLabel").is(":checked"),
         editEdgeLabel:$("#editEdgeLabel").is(":checked"),
         snapToGrid:$("#snapToGrid").is(":checked"),
         useGrid:$("#useGrid").is(":checked"),
         gridSize:$("#gridSize").val()
      };
      return JSON.stringify(obj);
   };

   subTask.getGrade = function(callback) {
      callback({
         successRate: 1
      });
   };
}
initWrapper(initTask, ["easy", "medium", "hard"]);
<commit_msg>graph-test-task: update<commit_after>function initTask(subTask) {
   var state = {};
   var level;
   var answer = null;

   var defaultVisualGraphJSON = {
      "vertexVisualInfo": {
         "v_0":{"x":64,"y":92},
         "v_1":{"x":169,"y":100},
         "v_2":{"x":212,"y":197},
         "v_3":{"x":113,"y":290} },
      "edgeVisualInfo": {
         "e_0":{},
         "e_1":{},
         "e_2":{},
         "e_3":{} },
      "minGraph": {
         "vertexInfo": {
            "v_0":{label:"v_0"},
            "v_1":{label:"v_1"},
            "v_2":{label:"v_2"},
            "v_3":{label:"v_3"} },
         "edgeInfo": {
            "e_0":{label:"e_0"},
            "e_1":{label:"e_1"},
            "e_2":{label:"e_2"},
            "e_3":{label:"e_3"} },
         "edgeVertices": {
            "e_0":["v_0","v_3"],
            "e_1":["v_3","v_2"],
            "e_2":["v_2","v_1"],
            "e_3":["v_2","v_0"]},
         "directed":true }
      };
   var defaultCircleAttr = {
      "r": 20,
      "fill": "#AA0000",
      "stroke": "black",
      "stroke-width": 1
   };
   var defaultLineAttr = {
      "stroke": "black",
      "stroke-width": 4,
      "arrow-end": "long-classic-wide"
   };

   var paperWidth = 750;
   var paperHeight = 500;

   var paper;
   var graph;
   var visualGraph;
   var graphMouse;
   var graphDrawer;
   var graphEditor;
   var snapToGrid;
   var dragOverlay;
   var dragInitialPositions;
   var dragInitialMouse;
   var keepProportions;

   var showGrid = true;
   var gridSize = 32;
   var grid;

   var vertexGuid = 0;
   var edgeGuid = 0;

   var circleAttr;
   var lineAttr;

   var selectedCircleAttr = {
      stroke: "blue",
      "stroke-width": 6
   };

   var gridLineAttr = {
      stroke: "black",
      opacity: 0.5,
      "stroke-width": 1
   };

   var vertexTextAttr = {
      "font-size": 16,
      "fill": "white"
   };

   subTask.loadLevel = function(curLevel) {
      level = curLevel;
      displayHelper.hideValidateButton = true;
   };

   subTask.getStateObject = function() {
      return state;
   };

   subTask.reloadAnswerObject = function(answerObj) {
      answer = answerObj;
   };

   subTask.resetDisplay = function() {
      initJSON();
      initPaper();
      initHandlers();
      applyAttr();
      fromJSON();
      updateJSON(false);
      updateGridVisibility(showGrid);
      updateInfo();
   };

   subTask.getAnswerObject = function() {
      answer.visual_json[answer.visual_json.length - 1] = $("#visual_json").val();
      answer.circleAttr = $("#circleAttr").val();
      answer.lineAttr = $("#lineAttr").val();
      return answer;
   };

   subTask.getDefaultAnswerObject = function() {
      return {
         visual_json: [JSON.stringify(defaultVisualGraphJSON)],
         circleAttr: JSON.stringify(defaultCircleAttr),
         lineAttr: JSON.stringify(defaultLineAttr)
      };
   };

   subTask.unloadLevel = function(callback) {
      if(visualGraph) {
         disableAllMouse();
      }
      callback();
   };

   var initJSON = function() {
      $("#visual_json").val(answer.visual_json[answer.visual_json.length - 1]);
      $("#circleAttr").val(JSON.stringify(JSON.parse(answer.circleAttr), null, 2));
      $("#lineAttr").val(JSON.stringify(JSON.parse(answer.lineAttr), null, 2));
   };

   var initHandlers = function() {
      $("#pretty").unbind('change');
      $("#pretty").change(updateJSON);
      $("#import").unbind('click');
      $("#import").click(fromJSON);
      $("#directed").unbind('change');
      $("#directed").change(function() {
         updateDirected(this.checked);
      });
      $("#createVertex").off("change");
      $("#createVertex").change(function(){
         // graphEditor.vertexCreator.setEnabled(this.checked);
         // graphEditor.removeVertexEnabled = this.checked;
         graphEditor.setCreateVertexEnabled(this.checked);
         updateOptionsJSON();
      });
      $("#createEdge").off("change");
      $("#createEdge").change(function(){
         // graphEditor.createEdgeEnabled = this.checked;
         // graphEditor.removeEdgeEnabled = this.checked;
         graphEditor.setCreateEdgeEnabled(this.checked);
         updateOptionsJSON();
      });
      $("#dragVertex").off("change");
      $("#dragVertex").change(function(){
         graphEditor.setVertexDragEnabled(this.checked);
         updateOptionsJSON();
      });
      $("#dragEdge").off("change");
      $("#dragEdge").change(function(){
         // graphEditor.arcDragger.setEnabled(this.checked);
         graphEditor.setEdgeDragEnabled(this.checked);
         updateOptionsJSON();
      });
      $("#multipleEdges").off("change");
      $("#multipleEdges").change(function(){
         graphEditor.setMultipleEdgesEnabled(this.checked);
         updateOptionsJSON();
      });
      $("#loop").off("change");
      $("#loop").change(function(){
         graphEditor.setLoopEnabled(this.checked);
         updateOptionsJSON();
      });
      $("#editVertexLabel").off("change");
      $("#editVertexLabel").change(function(){
         graphEditor.setEditVertexLabelEnabled(this.checked);
         updateOptionsJSON();
      });
      $("#editEdgeLabel").off("change");
      $("#editEdgeLabel").change(function(){
         graphEditor.setEditEdgeLabelEnabled(this.checked);
         updateOptionsJSON();
      });
      $("#dragGraph").off("change");
      $("#dragGraph").change(function(){
         graphEditor.setDragGraphEnabled(this.checked);
         updateOptionsJSON();
      });
      $("#scaleGraph").off("change");
      $("#scaleGraph").change(function(){
         graphEditor.setScaleGraphEnabled(this.checked);
         updateOptionsJSON();
      });
      $("#snapToGrid").change(function() {
         snapToGrid = this.checked;
         graphEditor.setGridEnabled(snapToGrid, gridSize, gridSize);
         updateOptionsJSON();
      });
      $("#useGrid").unbind('change');
      $("#useGrid").change(function() {
         updateGridVisibility(this.checked);
         updateOptionsJSON();
      });
      $("#gridSize").unbind('change');
      $("#gridSize").change(function() {
         updateGridSize(this.value);
         updateOptionsJSON();
      });
      $("#apply_attr").unbind('click');
      $("#apply_attr").click(applyAttr);
      $("#undo").unbind('click');
      $("#undo").click(clickUndo);
      $("#undo").attr("disabled", answer.visual_json.length == 1);
   };

   var clickUndo = function() {
      if(answer.visual_json.length == 1) {
         return;
      }
      answer.visual_json.pop();
      $("#visual_json").val(answer.visual_json[answer.visual_json.length - 1]);
      fromJSON();
      if(answer.visual_json.length == 1) {
         $("#undo").attr("disabled", true);
      }
   };

   var applyAttr = function() {
      circleAttr = JSON.parse($("#circleAttr").val());
      lineAttr = JSON.parse($("#lineAttr").val());
      if(visualGraph) {
         graphDrawer.setCircleAttr(circleAttr);
         graphDrawer.setLineAttr(lineAttr);
         visualGraph.redraw();
         disableAllMouse();
         graphEditor.setEnabled(true);
      }
   };

   var updateDirected = function(directed) {
      graph.directed = directed;
      updateJSON();
   };

   var updateGridVisibility = function(show) {
      showGrid = show;
      removeGrid();
      if(showGrid) {
         drawGrid();
      }
   };

   var updateGridSize = function(size) {
      size = parseInt(size);
      if (isNaN(size) || size <= 0) {
        return;
      }
      gridSize = size;
      updateGridVisibility(showGrid);

      if(snapToGrid) {
         graphEditor.setGridEnabled(true, gridSize, gridSize);
      }
      // TODO: keep track of the value in JSON?
   };

   var removeGrid = function() {
      if(grid) {
         grid.remove();
         grid = null;
      }
   };

   var drawGrid = function() {
      removeGrid();
      if(!showGrid) {
         return;
      }

      var rows = Math.ceil(paperHeight / gridSize);
      var cols = Math.ceil(paperWidth / gridSize);

      grid = new Grid("anim", paper, rows, cols, gridSize, gridSize, 0, 0, gridLineAttr);
      grid.linesToBack();
   };

   var disableAllMouse = function() {
      if(!graphEditor){
         return;
      }
      graphEditor.setEnabled(false);
   };

   var initPaper = function() {
      paper = subTask.raphaelFactory.create("anim", "anim", paperWidth, paperHeight);
      paper.rect(0, 0, paperWidth, paperHeight).attr("stroke-width", 2);
      dragOverlay = paper.rect(0, 0, paperWidth, paperHeight).attr({
         fill: "green",
         opacity: 0
      }).hide();
   };

   var onVisualGraphChange = function() {
      updateJSON(true);
      updateInfo();
   };

   var updateInfo = function() {
      var size = getVisualSize();
      var html = "Size: " + size.width + " x " + size.height + "<br>";
      var margins = getVisualMargins();
      html += "With symmetric margins: " + (size.width + margins.x) + " x " + (size.height + margins.y);
      $("#info").html(html);
   };

   var getVisualSize = function() {
      var maxX = 0;
      var maxY = 0;
      graph.forEachVertex(function (id) {
         var position = graphDrawer.getVertexPosition(id);
         maxX = Math.max(maxX, position.x + circleAttr.r);
         maxY = Math.max(maxY, position.y + circleAttr.r);
      });
      return {
         width: Math.ceil(maxX),
         height: Math.ceil(maxY)
      };
   };

   var getVisualMargins = function() {
      var minX = paperWidth;
      var minY = paperHeight;
      graph.forEachVertex(function (id) {
         var position = graphDrawer.getVertexPosition(id);
         minX = Math.min(minX, position.x - circleAttr.r);
         minY = Math.min(minY, position.y - circleAttr.r);
      });
      return {
         x: Math.floor(minX),
         y: Math.floor(minY)
      };
   };

   var fromJSON = function() {
      if(visualGraph) {
         disableAllMouse();
         visualGraph.remove();
      }
      graphDrawer = new SimpleGraphDrawer(circleAttr, lineAttr, null, true);
      graphDrawer.setVertexLabelAttr(vertexTextAttr);
      visualGraph = VisualGraph.fromJSON($("#visual_json").val(), "visual", paper, null, graphDrawer, true);
      graph = visualGraph.graph;
      graphMouse = new GraphMouse("mouse", graph, visualGraph);
      $("#directed").prop('checked', graph.directed);
      initVisuals();
   };

   var initVisuals = function() {
      var settings = {
         id: "graphEditor",
         paper: paper,
         graph: graph,
         paperElementID: "anim",
         visualGraph: visualGraph,
         graphMouse: graphMouse,
         dragThreshold: 10,
         edgeThreshold: 20,
         dragLimits: {
            minX: circleAttr.r,
            maxX: paperWidth - circleAttr.r,
            minY: circleAttr.r,
            maxY: paperHeight - circleAttr.r
         },
         vertexLabelAttr: vertexTextAttr,
         onDragEnd: onVisualGraphChange,
         callback: onVisualGraphChange,
         enabled: true
      };
      graphEditor = new GraphEditor(settings);
   };

   var updateJSON = function(updateAnswer) {
      updateOptionsJSON();
      var visualJSON = visualGraph.toJSON();
      if($("#pretty").is(":checked")) {
         visualJSON = JSON.stringify(JSON.parse(visualJSON), null, 2);
      }
      $("#visual_json").val(visualJSON);
      if(updateAnswer) {
         answer.visual_json.push(visualJSON);
         $("#undo").attr("disabled", false);
      }
   };

   var updateOptionsJSON = function() {
      var optionsJSON = optionsToJSON();
      if($("#pretty").is(":checked")) {
         optionsJSON = JSON.stringify(JSON.parse(optionsJSON), null, 2);
      }
      $("#options_json").val(optionsJSON);
   };

   function optionsToJSON() {
      var obj = {
         createVertex:$("#createVertex").is(":checked"),
         createEdge:$("#createEdge").is(":checked"),
         dragVertex:$("#dragVertex").is(":checked"),
         dragEdge:$("#dragEdge").is(":checked"),
         dragGraph:$("#dragGraph").is(":checked"),
         scaleGraph:$("#scaleGraph").is(":checked"),
         multipleEdges:$("#multipleEdges").is(":checked"),
         loop:$("#loop").is(":checked"),
         editVertexLabel:$("#editVertexLabel").is(":checked"),
         editEdgeLabel:$("#editEdgeLabel").is(":checked"),
         snapToGrid:$("#snapToGrid").is(":checked"),
         useGrid:$("#useGrid").is(":checked"),
         gridSize:$("#gridSize").val()
      };
      return JSON.stringify(obj);
   };

   subTask.getGrade = function(callback) {
      callback({
         successRate: 1
      });
   };
}
initWrapper(initTask, ["easy", "medium", "hard"]);
<|endoftext|>


<commit_before>'use strict';

var Recognizer = function(canvas) {
  var recognizer = this;

  var initialize = (function(){
    recognizer.canvas = canvas;
    recognizer.context = canvas.getContext('2d');

    recognizer.context.lineWidth = 10;
    recognizer.context.lineJoin = recognizer.context.lineCap = 'round';

    recognizer.moves = [];
  })();

  var clear = function(){
    recognizer.context.clearRect(0, 0, recognizer.context.canvas.width, recognizer.context.canvas.height);
    recognizer.moves = [];
  };

  var midPointBtw = function(p1, p2) {
    return {
      x: p1.x + (p2.x - p1.x) / 2,
      y: p1.y + (p2.y - p1.y) / 2
    };
  }

  var redraw = function(e){

    recognizer.context.clearRect(0, 0, recognizer.context.canvas.width, recognizer.context.canvas.height);

    for (var m = 0; m < recognizer.moves.length; m++) {

      var points = recognizer.moves[m];

      var p1 = points[0];
      var p2 = points[1];

      recognizer.context.beginPath();
      recognizer.context.moveTo(p1.x, p1.y);

      for (var i = 1, len = points.length; i < len; i++) {
        var midPoint = midPointBtw(p1, p2);
        recognizer.context.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y);
        p1 = points[i];
        p2 = points[i+1];
      }

      recognizer.context.lineTo(p1.x, p1.y);
      recognizer.context.stroke();

    }
  }

  var mousedown = function(e){
    recognizer.isDrawing = true;
    recognizer.moves.push([]);
    var points = recognizer.moves[recognizer.moves.length - 1]
    points.push({ x: e.clientX - recognizer.canvas.clientLeft - recognizer.canvas.offsetLeft, y: e.clientY - recognizer.canvas.clientTop - recognizer.canvas.offsetTop });
  };

  var mouseup = function(e){
    recognizer.isDrawing = false;
  };

  var mousemove = function(e){
    if (recognizer.isDrawing) {

      var points = recognizer.moves[recognizer.moves.length - 1]
      points.push({ x: e.clientX - recognizer.canvas.clientLeft - recognizer.canvas.offsetLeft, y: e.clientY - recognizer.canvas.clientTop - recognizer.canvas.offsetTop });
      redraw(e);
    }
  };

  /**
   * Invariants
   */

  var isBlack = function(r, g, b, a){
    if (r === 0 && g === 0 && b === 0 && a === 255) {
      return true;
    }
    else {
      return false;
    }
  };

  var getBlackPixels = function(){

    var blackPixels = 0;
    var totalPixels = recognizer.context.canvas.width * recognizer.context.canvas.height;

    var imgDate = recognizer.context.getImageData(0, 0, recognizer.context.canvas.width, recognizer.context.canvas.height);
    var pixels = imgDate.data;

    for (var row = 0; row < recognizer.context.canvas.height; row++){
      for (var collumn = 0; collumn < recognizer.context.canvas.width; collumn++){
        var pixelIndex = (row * recognizer.context.canvas.width + collumn) * 4;

        var r = pixels[pixelIndex];
        var g = pixels[pixelIndex + 1];
        var b = pixels[pixelIndex + 2];
        var a = pixels[pixelIndex + 3];

        if (isBlack(r, g, b, a)){
          blackPixels++;
        }
      }
    }

    return blackPixels / totalPixels;
  };

  var getAvgDistance = function(distances){
    var sum = 0;
    for (var i=0; i<distances.length; i++){
      sum += distances[i];
    }
    return sum / distances.length;
  };

  var getMedianDistance = function(distances){
    var median = 0;
    distances.sort();

    if (distances.length % 2 === 0) {
      median = (distances[distances.length / 2 - 1] + distances[distances.length / 2]) / 2;
    }
    else {
      median = distances[(distances.length - 1) / 2];
    }

    return median;
  };

  var getManhattanDistance = function(x1, y1, xCenter, yCenter){
    return Math.abs(x1 - xCenter) + Math.abs(y1 - yCenter);;
  };

  var getCenterDistance = function(){

    var distances = [];

    var imgDate = recognizer.context.getImageData(0, 0, recognizer.context.canvas.width, recognizer.context.canvas.height);
    var pixels = imgDate.data;

    for (var row = 0; row < recognizer.context.canvas.height; row++){
      for (var collumn = 0; collumn < recognizer.context.canvas.width; collumn++){
        var pixelIndex = (row * recognizer.context.canvas.width + collumn) * 4;

        var r = pixels[pixelIndex];
        var g = pixels[pixelIndex + 1];
        var b = pixels[pixelIndex + 2];
        var a = pixels[pixelIndex + 3];

        if (isBlack(r, g, b, a)){
          distances.push(getManhattanDistance(collumn, row, Math.floor(recognizer.context.canvas.width / 2), Math.floor(recognizer.context.canvas.height / 2)))
        }
      }
    }

    return {
      'avg': getAvgDistance(distances),
      'median': getMedianDistance(distances),
    };
  };

  var getPixels = function(){
    var SAMPLE = 10;
    var imgDate = recognizer.context.getImageData(0, 0, recognizer.context.canvas.width, recognizer.context.canvas.height);
    var pixels = imgDate.data;

    var data = [];

    for (var row = 0; row < recognizer.context.canvas.height / SAMPLE; row++){
      for (var collumn = 0; collumn < recognizer.context.canvas.width / SAMPLE; collumn++){

        var blackPixels = 0;
        for (var y = 0; y < SAMPLE; y++) {
          for (var x = 0; x < SAMPLE; x++) {
            var pixelIndex = ((row * SAMPLE + y) * recognizer.context.canvas.width + collumn * SAMPLE + x) * 4;

            var r = pixels[pixelIndex];
            var g = pixels[pixelIndex + 1];
            var b = pixels[pixelIndex + 2];
            var a = pixels[pixelIndex + 3];

            if (isBlack(r, g, b, a)){
              blackPixels++;
            }
          }
        }

        console.log(blackPixels);
        if (blackPixels / (SAMPLE * SAMPLE) > 0.1) {
          data.push(true);
        }
        else {
          data.push(false);
        }

      }
    }

    return {
      'width': recognizer.context.canvas.width / SAMPLE,
      'height': recognizer.context.canvas.height / SAMPLE,
      'data': data
    };
  };

  /**
   * Recognizer
   */

  recognizer.canvas.onmousedown = mousedown;
  recognizer.canvas.onmouseup = mouseup;
  recognizer.canvas.onmousemove = mousemove;

  recognizer.clear = clear;

  recognizer.getInvariants = function(){

    var blackPixels = getBlackPixels();
    var pixels = getPixels();
    var centerdistance = getCenterDistance();
    return {
      'strokes': recognizer.moves.length,
      'blackPixels': blackPixels,
      'avgDistance': centerdistance.avg,
      'medianDistance': centerdistance.median,
      'pixels': pixels
    }
  };

  return recognizer;
};

<commit_msg>feat: pixel map uses 0 and 1<commit_after>'use strict';

var Recognizer = function(canvas) {
  var recognizer = this;

  var initialize = (function(){
    recognizer.canvas = canvas;
    recognizer.context = canvas.getContext('2d');

    recognizer.context.lineWidth = 10;
    recognizer.context.lineJoin = recognizer.context.lineCap = 'round';

    recognizer.moves = [];
  })();

  var clear = function(){
    recognizer.context.clearRect(0, 0, recognizer.context.canvas.width, recognizer.context.canvas.height);
    recognizer.moves = [];
  };

  var midPointBtw = function(p1, p2) {
    return {
      x: p1.x + (p2.x - p1.x) / 2,
      y: p1.y + (p2.y - p1.y) / 2
    };
  }

  var redraw = function(e){

    recognizer.context.clearRect(0, 0, recognizer.context.canvas.width, recognizer.context.canvas.height);

    for (var m = 0; m < recognizer.moves.length; m++) {

      var points = recognizer.moves[m];

      var p1 = points[0];
      var p2 = points[1];

      recognizer.context.beginPath();
      recognizer.context.moveTo(p1.x, p1.y);

      for (var i = 1, len = points.length; i < len; i++) {
        var midPoint = midPointBtw(p1, p2);
        recognizer.context.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y);
        p1 = points[i];
        p2 = points[i+1];
      }

      recognizer.context.lineTo(p1.x, p1.y);
      recognizer.context.stroke();

    }
  }

  var mousedown = function(e){
    recognizer.isDrawing = true;
    recognizer.moves.push([]);
    var points = recognizer.moves[recognizer.moves.length - 1]
    points.push({ x: e.clientX - recognizer.canvas.clientLeft - recognizer.canvas.offsetLeft, y: e.clientY - recognizer.canvas.clientTop - recognizer.canvas.offsetTop });
  };

  var mouseup = function(e){
    recognizer.isDrawing = false;
  };

  var mousemove = function(e){
    if (recognizer.isDrawing) {

      var points = recognizer.moves[recognizer.moves.length - 1]
      points.push({ x: e.clientX - recognizer.canvas.clientLeft - recognizer.canvas.offsetLeft, y: e.clientY - recognizer.canvas.clientTop - recognizer.canvas.offsetTop });
      redraw(e);
    }
  };

  /**
   * Invariants
   */

  var isBlack = function(r, g, b, a){
    if (r === 0 && g === 0 && b === 0 && a === 255) {
      return true;
    }
    else {
      return false;
    }
  };

  var getBlackPixels = function(){

    var blackPixels = 0;
    var totalPixels = recognizer.context.canvas.width * recognizer.context.canvas.height;

    var imgDate = recognizer.context.getImageData(0, 0, recognizer.context.canvas.width, recognizer.context.canvas.height);
    var pixels = imgDate.data;

    for (var row = 0; row < recognizer.context.canvas.height; row++){
      for (var collumn = 0; collumn < recognizer.context.canvas.width; collumn++){
        var pixelIndex = (row * recognizer.context.canvas.width + collumn) * 4;

        var r = pixels[pixelIndex];
        var g = pixels[pixelIndex + 1];
        var b = pixels[pixelIndex + 2];
        var a = pixels[pixelIndex + 3];

        if (isBlack(r, g, b, a)){
          blackPixels++;
        }
      }
    }

    return blackPixels / totalPixels;
  };

  var getAvgDistance = function(distances){
    var sum = 0;
    for (var i=0; i<distances.length; i++){
      sum += distances[i];
    }
    return sum / distances.length;
  };

  var getMedianDistance = function(distances){
    var median = 0;
    distances.sort();

    if (distances.length % 2 === 0) {
      median = (distances[distances.length / 2 - 1] + distances[distances.length / 2]) / 2;
    }
    else {
      median = distances[(distances.length - 1) / 2];
    }

    return median;
  };

  var getManhattanDistance = function(x1, y1, xCenter, yCenter){
    return Math.abs(x1 - xCenter) + Math.abs(y1 - yCenter);;
  };

  var getCenterDistance = function(){

    var distances = [];

    var imgDate = recognizer.context.getImageData(0, 0, recognizer.context.canvas.width, recognizer.context.canvas.height);
    var pixels = imgDate.data;

    for (var row = 0; row < recognizer.context.canvas.height; row++){
      for (var collumn = 0; collumn < recognizer.context.canvas.width; collumn++){
        var pixelIndex = (row * recognizer.context.canvas.width + collumn) * 4;

        var r = pixels[pixelIndex];
        var g = pixels[pixelIndex + 1];
        var b = pixels[pixelIndex + 2];
        var a = pixels[pixelIndex + 3];

        if (isBlack(r, g, b, a)){
          distances.push(getManhattanDistance(collumn, row, Math.floor(recognizer.context.canvas.width / 2), Math.floor(recognizer.context.canvas.height / 2)))
        }
      }
    }

    return {
      'avg': getAvgDistance(distances),
      'median': getMedianDistance(distances),
    };
  };

  var getPixels = function(){
    var SAMPLE = 10;
    var imgDate = recognizer.context.getImageData(0, 0, recognizer.context.canvas.width, recognizer.context.canvas.height);
    var pixels = imgDate.data;

    var data = [];

    for (var row = 0; row < recognizer.context.canvas.height / SAMPLE; row++){
      for (var collumn = 0; collumn < recognizer.context.canvas.width / SAMPLE; collumn++){

        var blackPixels = 0;
        for (var y = 0; y < SAMPLE; y++) {
          for (var x = 0; x < SAMPLE; x++) {
            var pixelIndex = ((row * SAMPLE + y) * recognizer.context.canvas.width + collumn * SAMPLE + x) * 4;

            var r = pixels[pixelIndex];
            var g = pixels[pixelIndex + 1];
            var b = pixels[pixelIndex + 2];
            var a = pixels[pixelIndex + 3];

            if (isBlack(r, g, b, a)){
              blackPixels++;
            }
          }
        }

        if (blackPixels / (SAMPLE * SAMPLE) > 0.1) {
          data.push(1);
        }
        else {
          data.push(0);
        }

      }
    }

    return {
      'width': recognizer.context.canvas.width / SAMPLE,
      'height': recognizer.context.canvas.height / SAMPLE,
      'data': data
    };
  };

  /**
   * Recognizer
   */

  recognizer.canvas.onmousedown = mousedown;
  recognizer.canvas.onmouseup = mouseup;
  recognizer.canvas.onmousemove = mousemove;

  recognizer.clear = clear;

  recognizer.getInvariants = function(){

    var blackPixels = getBlackPixels();
    var pixels = getPixels();
    var centerdistance = getCenterDistance();
    return {
      'strokes': recognizer.moves.length,
      'blackPixels': blackPixels,
      'avgDistance': centerdistance.avg,
      'medianDistance': centerdistance.median,
      'pixels': pixels
    }
  };

  return recognizer;
};

<|endoftext|>


<commit_before>const assert = require('assert');
const ObjectMD = require('../../../lib/models/ObjectMD');
const constants = require('../../../lib/constants');

const retainDate = new Date();
retainDate.setDate(retainDate.getDate() + 1);

describe('ObjectMD class setters/getters', () => {
    let md = null;

    beforeEach(() => {
        md = new ObjectMD();
    });

    [
        // In order: data property, value to set/get, default value
        ['OwnerDisplayName', null, ''],
        ['OwnerDisplayName', 'owner-display-name'],
        ['OwnerId', null, ''],
        ['OwnerId', 'owner-id'],
        ['CacheControl', null, ''],
        ['CacheControl', 'cache-control'],
        ['ContentDisposition', null, ''],
        ['ContentDisposition', 'content-disposition'],
        ['ContentEncoding', null, ''],
        ['ContentEncoding', 'content-encoding'],
        ['Expires', null, ''],
        ['Expires', 'expire-date'],
        ['ContentLength', null, 0],
        ['ContentLength', 15000],
        ['ContentType', null, ''],
        ['ContentType', 'content-type'],
        ['LastModified', new Date().toJSON()],
        ['ContentMd5', null, ''],
        ['ContentMd5', 'content-md5'],
        ['ContentLanguage', null, ''],
        ['ContentLanguage', 'content-language', ''],
        ['CreationTime', new Date().toJSON()],
        ['AmzVersionId', null, 'null'],
        ['AmzVersionId', 'version-id'],
        ['AmzServerVersionId', null, ''],
        ['AmzServerVersionId', 'server-version-id'],
        ['AmzStorageClass', null, 'STANDARD'],
        ['AmzStorageClass', 'storage-class'],
        ['AmzServerSideEncryption', null, ''],
        ['AmzServerSideEncryption', 'server-side-encryption'],
        ['AmzEncryptionKeyId', null, ''],
        ['AmzEncryptionKeyId', 'encryption-key-id'],
        ['AmzEncryptionCustomerAlgorithm', null, ''],
        ['AmzEncryptionCustomerAlgorithm', 'customer-algorithm'],
        ['Acl', null, {
            Canned: 'private',
            FULL_CONTROL: [],
            WRITE_ACP: [],
            READ: [],
            READ_ACP: [],
        }],
        ['Acl', {
            Canned: 'public',
            FULL_CONTROL: ['id'],
            WRITE_ACP: ['id'],
            READ: ['id'],
            READ_ACP: ['id'],
        }],
        ['Key', null, ''],
        ['Key', 'key'],
        ['Location', null, []],
        ['Location', ['location1']],
        ['IsNull', null, false],
        ['IsNull', true],
        ['NullVersionId', null, undefined],
        ['NullVersionId', '111111'],
        ['IsDeleteMarker', null, false],
        ['IsDeleteMarker', true],
        ['VersionId', null, undefined],
        ['VersionId', '111111'],
        ['Tags', null, {}],
        ['Tags', {
            key: 'value',
        }],
        ['Tags', null, {}],
        ['ReplicationInfo', null, {
            status: '',
            backends: [],
            content: [],
            destination: '',
            storageClass: '',
            role: '',
            storageType: '',
            dataStoreVersionId: '',
            isNFS: null,
        }],
        ['ReplicationInfo', {
            status: 'PENDING',
            backends: [{
                site: 'zenko',
                status: 'PENDING',
                dataStoreVersionId: 'a',
            }],
            content: ['DATA', 'METADATA'],
            destination: 'destination-bucket',
            storageClass: 'STANDARD',
            role: 'arn:aws:iam::account-id:role/src-resource,' +
                'arn:aws:iam::account-id:role/dest-resource',
            storageType: 'aws_s3',
            dataStoreVersionId: '',
            isNFS: null,
        }],
        ['DataStoreName', null, ''],
        ['ReplicationIsNFS', null, null],
        ['ReplicationIsNFS', true],
        ['AzureInfo', {
            containerPublicAccess: 'container',
            containerStoredAccessPolicies: [],
            containerImmutabilityPolicy: {},
            containerLegalHoldStatus: false,
            containerDeletionInProgress: false,
            blobType: 'BlockBlob',
            blobContentMD5: 'ABCDEF==',
            blobCopyInfo: {},
            blobSequenceNumber: 42,
            blobAccessTierChangeTime: 'abcdef',
            blobUncommitted: false,
        }],
        ['LegalHold', null, false],
        ['LegalHold', true],
        ['RetentionInfo', {
            mode: 'GOVERNANCE',
            retainUntilDate: retainDate.toISOString(),
        }],
    ].forEach(test => {
        const property = test[0];
        const testValue = test[1];
        const defaultValue = test[2];
        const testName = testValue === null ? 'get default' : 'get/set';
        it(`${testName}: ${property}`, () => {
            if (testValue !== null) {
                md[`set${property}`](testValue);
            }
            const value = md[`get${property}`]();
            if ((testValue !== null && typeof testValue === 'object') ||
                typeof defaultValue === 'object') {
                assert.deepStrictEqual(value, testValue || defaultValue);
            } else if (testValue !== null) {
                assert.strictEqual(value, testValue);
            } else {
                assert.strictEqual(value, defaultValue);
            }
        });
    });

    it('ObjectMD::setReplicationSiteStatus', () => {
        md.setReplicationInfo({
            backends: [{
                site: 'zenko',
                status: 'PENDING',
                dataStoreVersionId: 'a',
            }],
        });
        md.setReplicationSiteStatus('zenko', 'COMPLETED');
        assert.deepStrictEqual(md.getReplicationInfo().backends, [{
            site: 'zenko',
            status: 'COMPLETED',
            dataStoreVersionId: 'a',
        }]);
    });

    it('ObjectMD::setReplicationBackends', () => {
        md.setReplicationBackends([{
            site: 'a',
            status: 'b',
            dataStoreVersionId: 'c',
        }]);
        assert.deepStrictEqual(md.getReplicationBackends(), [{
            site: 'a',
            status: 'b',
            dataStoreVersionId: 'c',
        }]);
    });

    it('ObjectMD::setReplicationStorageType', () => {
        md.setReplicationStorageType('a');
        assert.strictEqual(md.getReplicationStorageType(), 'a');
    });

    it('ObjectMD::setReplicationStorageClass', () => {
        md.setReplicationStorageClass('a');
        assert.strictEqual(md.getReplicationStorageClass(), 'a');
    });

    it('ObjectMD::getReplicationSiteStatus', () => {
        md.setReplicationInfo({
            backends: [{
                site: 'zenko',
                status: 'PENDING',
                dataStoreVersionId: 'a',
            }],
        });
        assert.strictEqual(md.getReplicationSiteStatus('zenko'), 'PENDING');
    });

    it('ObjectMD::setReplicationSiteDataStoreVersionId', () => {
        md.setReplicationInfo({
            backends: [{
                site: 'zenko',
                status: 'PENDING',
                dataStoreVersionId: 'a',
            }],
        });
        md.setReplicationSiteDataStoreVersionId('zenko', 'b');
        assert.deepStrictEqual(md.getReplicationInfo().backends, [{
            site: 'zenko',
            status: 'PENDING',
            dataStoreVersionId: 'b',
        }]);
    });

    it('ObjectMD::getReplicationSiteDataStoreVersionId', () => {
        md.setReplicationInfo({
            backends: [{
                site: 'zenko',
                status: 'PENDING',
                dataStoreVersionId: 'a',
            }],
        });
        assert.strictEqual(
            md.getReplicationSiteDataStoreVersionId('zenko'), 'a');
    });

<<<<<<< HEAD:tests/unit/models/ObjectMD.js
    it('ObjectMd::isMultipartUpload', () => {
        md.setContentMd5('68b329da9893e34099c7d8ad5cb9c940');
        assert.strictEqual(md.isMultipartUpload(), false);
        md.setContentMd5('741e0f4bad5b093044dc54a74d911094-1');
        assert.strictEqual(md.isMultipartUpload(), true);
        md.setContentMd5('bda0c0bed89c8bdb9e409df7ae7073c5-9876');
        assert.strictEqual(md.isMultipartUpload(), true);
    });

    it('ObjectMD::getUserMetadata', () => {
        md.setUserMetadata({
            'x-amz-meta-foo': 'bar',
            'x-amz-meta-baz': 'qux',
            // This one should be filtered out
            'x-amz-storage-class': 'STANDARD_IA',
            // This one should be changed to 'x-amz-meta-foobar'
            'x-ms-meta-foobar': 'bar',
            // ACLs are updated
            'acl': {
                FULL_CONTROL: ['john'],
            },
        });
        assert.deepStrictEqual(JSON.parse(md.getUserMetadata()), {
            'x-amz-meta-foo': 'bar',
            'x-amz-meta-baz': 'qux',
            'x-amz-meta-foobar': 'bar',
        });
        assert.deepStrictEqual(md.getAcl(), {
            FULL_CONTROL: ['john'],
        });
    });

    it('ObjectMD:clearMetadataValues', () => {
        md.setUserMetadata({
            'x-amz-meta-foo': 'bar',
        });
        md.clearMetadataValues();
        assert.strictEqual(md.getUserMetadata(), undefined);
    });

    it('ObjectMD::microVersionId unset', () => {
        assert.strictEqual(md.getMicroVersionId(), null);
    });

    it('ObjectMD::microVersionId set', () => {
        const generatedIds = new Set();
        for (let i = 0; i < 100; ++i) {
            md.updateMicroVersionId();
            generatedIds.add(md.getMicroVersionId());
        }
        // all generated IDs should be different
        assert.strictEqual(generatedIds.size, 100);
        generatedIds.forEach(key => {
            // length is always 16 in hex because leading 0s are
            // also encoded in the 8-byte random buffer.
            assert.strictEqual(key.length, 16);
        });
    });

    it('ObjectMD::set/getRetentionInfo', () => {
        md.setRetentionInfo({
            mode: 'COMPLIANCE',
            retainUntilDate: retainDate.toISOString(),
        });
        assert.deepStrictEqual(md.getRetentionInfo(), {
            mode: 'COMPLIANCE',
            retainUntilDate: retainDate.toISOString(),
        });
    });
});

describe('ObjectMD import from stored blob', () => {
    it('should export and import correctly the latest model version', () => {
        const md = new ObjectMD();
        const jsonMd = md.getSerialized();
        const importedRes = ObjectMD.createFromBlob(jsonMd);
        assert.ifError(importedRes.error);
        const importedMd = importedRes.result;
        assert.deepStrictEqual(md, importedMd);
    });

    it('should convert old location to new location', () => {
        const md = new ObjectMD();
        const value = md.getValue();
        value['md-model-version'] = 1;
        value.location = 'stringLocation';
        const jsonMd = JSON.stringify(value);
        const importedRes = ObjectMD.createFromBlob(jsonMd);
        assert.strictEqual(importedRes.error, undefined);
        const importedMd = importedRes.result;
        const valueImported = importedMd.getValue();
        assert.strictEqual(valueImported['md-model-version'],
                           constants.mdModelVersion);
        assert.deepStrictEqual(valueImported.location,
                               [{ key: 'stringLocation' }]);
    });

    it('should keep null location as is', () => {
        const md = new ObjectMD();
        const value = md.getValue();
        value.location = null;
        const jsonMd = JSON.stringify(value);
        const importedRes = ObjectMD.createFromBlob(jsonMd);
        assert.strictEqual(importedRes.error, undefined);
        const importedMd = importedRes.result;
        const valueImported = importedMd.getValue();
        assert.deepStrictEqual(valueImported.location, null);
        importedMd.setLocation([]);
        assert.deepStrictEqual(importedMd.getValue().location, null);
    });

    it('should add dataStoreName attribute if missing', () => {
        const md = new ObjectMD();
        const value = md.getValue();
        value['md-model-version'] = 2;
        delete value.dataStoreName;
        const jsonMd = JSON.stringify(value);
        const importedRes = ObjectMD.createFromBlob(jsonMd);
        assert.strictEqual(importedRes.error, undefined);
        const importedMd = importedRes.result;
        const valueImported = importedMd.getValue();
        assert.strictEqual(valueImported['md-model-version'],
                           constants.mdModelVersion);
        assert.notStrictEqual(valueImported.dataStoreName, undefined);
    });

    it('should return undefined for dataStoreVersionId if no object location',
    () => {
        const md = new ObjectMD();
        const value = md.getValue();
        const jsonMd = JSON.stringify(value);
        const importedRes = ObjectMD.createFromBlob(jsonMd);
        assert.strictEqual(importedRes.error, undefined);
        const importedMd = importedRes.result;
        assert.strictEqual(importedMd.getDataStoreVersionId(), undefined);
    });

    it('should get dataStoreVersionId if saved in object location', () => {
        const md = new ObjectMD();
        const dummyLocation = {
            dataStoreVersionId: 'data-store-version-id',
        };
        md.setLocation([dummyLocation]);
        const value = md.getValue();
        const jsonMd = JSON.stringify(value);
        const importedRes = ObjectMD.createFromBlob(jsonMd);
        assert.strictEqual(importedRes.error, undefined);
        const importedMd = importedRes.result;
        assert.strictEqual(importedMd.getDataStoreVersionId(),
            dummyLocation.dataStoreVersionId);
    });

    it('should return an error if blob is malformed JSON', () => {
        const importedRes = ObjectMD.createFromBlob('{BAD JSON}');
        assert.notStrictEqual(importedRes.error, undefined);
        assert.strictEqual(importedRes.result, undefined);
    });
});

describe('getAttributes static method', () => {
    it('should return object metadata attributes', () => {
        const attributes = ObjectMD.getAttributes();
        const expectedResult = {
            'owner-display-name': true,
            'owner-id': true,
            'cache-control': true,
            'content-disposition': true,
            'content-encoding': true,
            'expires': true,
            'content-length': true,
            'content-type': true,
            'content-md5': true,
            'content-language': true,
            'creation-time': true,
            'x-amz-version-id': true,
            'x-amz-server-version-id': true,
            'x-amz-storage-class': true,
            'x-amz-server-side-encryption': true,
            'x-amz-server-side-encryption-aws-kms-key-id': true,
            'x-amz-server-side-encryption-customer-algorithm': true,
            'x-amz-website-redirect-location': true,
            'acl': true,
            'key': true,
            'location': true,
            'azureInfo': true,
            'isNull': true,
            'nullVersionId': true,
            'isDeleteMarker': true,
            'versionId': true,
            'tags': true,
            'replicationInfo': true,
            'dataStoreName': true,
            'last-modified': true,
            'md-model-version': true };
        assert.deepStrictEqual(attributes, expectedResult);
    });
});
<commit_msg>fix linter<commit_after>const assert = require('assert');
const ObjectMD = require('../../../lib/models/ObjectMD');
const constants = require('../../../lib/constants');

const retainDate = new Date();
retainDate.setDate(retainDate.getDate() + 1);

describe('ObjectMD class setters/getters', () => {
    let md = null;

    beforeEach(() => {
        md = new ObjectMD();
    });

    [
        // In order: data property, value to set/get, default value
        ['OwnerDisplayName', null, ''],
        ['OwnerDisplayName', 'owner-display-name'],
        ['OwnerId', null, ''],
        ['OwnerId', 'owner-id'],
        ['CacheControl', null, ''],
        ['CacheControl', 'cache-control'],
        ['ContentDisposition', null, ''],
        ['ContentDisposition', 'content-disposition'],
        ['ContentEncoding', null, ''],
        ['ContentEncoding', 'content-encoding'],
        ['Expires', null, ''],
        ['Expires', 'expire-date'],
        ['ContentLength', null, 0],
        ['ContentLength', 15000],
        ['ContentType', null, ''],
        ['ContentType', 'content-type'],
        ['LastModified', new Date().toJSON()],
        ['ContentMd5', null, ''],
        ['ContentMd5', 'content-md5'],
        ['ContentLanguage', null, ''],
        ['ContentLanguage', 'content-language', ''],
        ['CreationTime', new Date().toJSON()],
        ['AmzVersionId', null, 'null'],
        ['AmzVersionId', 'version-id'],
        ['AmzServerVersionId', null, ''],
        ['AmzServerVersionId', 'server-version-id'],
        ['AmzStorageClass', null, 'STANDARD'],
        ['AmzStorageClass', 'storage-class'],
        ['AmzServerSideEncryption', null, ''],
        ['AmzServerSideEncryption', 'server-side-encryption'],
        ['AmzEncryptionKeyId', null, ''],
        ['AmzEncryptionKeyId', 'encryption-key-id'],
        ['AmzEncryptionCustomerAlgorithm', null, ''],
        ['AmzEncryptionCustomerAlgorithm', 'customer-algorithm'],
        ['Acl', null, {
            Canned: 'private',
            FULL_CONTROL: [],
            WRITE_ACP: [],
            READ: [],
            READ_ACP: [],
        }],
        ['Acl', {
            Canned: 'public',
            FULL_CONTROL: ['id'],
            WRITE_ACP: ['id'],
            READ: ['id'],
            READ_ACP: ['id'],
        }],
        ['Key', null, ''],
        ['Key', 'key'],
        ['Location', null, []],
        ['Location', ['location1']],
        ['IsNull', null, false],
        ['IsNull', true],
        ['NullVersionId', null, undefined],
        ['NullVersionId', '111111'],
        ['IsDeleteMarker', null, false],
        ['IsDeleteMarker', true],
        ['VersionId', null, undefined],
        ['VersionId', '111111'],
        ['Tags', null, {}],
        ['Tags', {
            key: 'value',
        }],
        ['Tags', null, {}],
        ['ReplicationInfo', null, {
            status: '',
            backends: [],
            content: [],
            destination: '',
            storageClass: '',
            role: '',
            storageType: '',
            dataStoreVersionId: '',
            isNFS: null,
        }],
        ['ReplicationInfo', {
            status: 'PENDING',
            backends: [{
                site: 'zenko',
                status: 'PENDING',
                dataStoreVersionId: 'a',
            }],
            content: ['DATA', 'METADATA'],
            destination: 'destination-bucket',
            storageClass: 'STANDARD',
            role: 'arn:aws:iam::account-id:role/src-resource,' +
                'arn:aws:iam::account-id:role/dest-resource',
            storageType: 'aws_s3',
            dataStoreVersionId: '',
            isNFS: null,
        }],
        ['DataStoreName', null, ''],
        ['ReplicationIsNFS', null, null],
        ['ReplicationIsNFS', true],
        ['AzureInfo', {
            containerPublicAccess: 'container',
            containerStoredAccessPolicies: [],
            containerImmutabilityPolicy: {},
            containerLegalHoldStatus: false,
            containerDeletionInProgress: false,
            blobType: 'BlockBlob',
            blobContentMD5: 'ABCDEF==',
            blobCopyInfo: {},
            blobSequenceNumber: 42,
            blobAccessTierChangeTime: 'abcdef',
            blobUncommitted: false,
        }],
        ['LegalHold', null, false],
        ['LegalHold', true],
        ['RetentionInfo', {
            mode: 'GOVERNANCE',
            retainUntilDate: retainDate.toISOString(),
        }],
    ].forEach(test => {
        const property = test[0];
        const testValue = test[1];
        const defaultValue = test[2];
        const testName = testValue === null ? 'get default' : 'get/set';
        it(`${testName}: ${property}`, () => {
            if (testValue !== null) {
                md[`set${property}`](testValue);
            }
            const value = md[`get${property}`]();
            if ((testValue !== null && typeof testValue === 'object') ||
                typeof defaultValue === 'object') {
                assert.deepStrictEqual(value, testValue || defaultValue);
            } else if (testValue !== null) {
                assert.strictEqual(value, testValue);
            } else {
                assert.strictEqual(value, defaultValue);
            }
        });
    });

    it('ObjectMD::setReplicationSiteStatus', () => {
        md.setReplicationInfo({
            backends: [{
                site: 'zenko',
                status: 'PENDING',
                dataStoreVersionId: 'a',
            }],
        });
        md.setReplicationSiteStatus('zenko', 'COMPLETED');
        assert.deepStrictEqual(md.getReplicationInfo().backends, [{
            site: 'zenko',
            status: 'COMPLETED',
            dataStoreVersionId: 'a',
        }]);
    });

    it('ObjectMD::setReplicationBackends', () => {
        md.setReplicationBackends([{
            site: 'a',
            status: 'b',
            dataStoreVersionId: 'c',
        }]);
        assert.deepStrictEqual(md.getReplicationBackends(), [{
            site: 'a',
            status: 'b',
            dataStoreVersionId: 'c',
        }]);
    });

    it('ObjectMD::setReplicationStorageType', () => {
        md.setReplicationStorageType('a');
        assert.strictEqual(md.getReplicationStorageType(), 'a');
    });

    it('ObjectMD::setReplicationStorageClass', () => {
        md.setReplicationStorageClass('a');
        assert.strictEqual(md.getReplicationStorageClass(), 'a');
    });

    it('ObjectMD::getReplicationSiteStatus', () => {
        md.setReplicationInfo({
            backends: [{
                site: 'zenko',
                status: 'PENDING',
                dataStoreVersionId: 'a',
            }],
        });
        assert.strictEqual(md.getReplicationSiteStatus('zenko'), 'PENDING');
    });

    it('ObjectMD::setReplicationSiteDataStoreVersionId', () => {
        md.setReplicationInfo({
            backends: [{
                site: 'zenko',
                status: 'PENDING',
                dataStoreVersionId: 'a',
            }],
        });
        md.setReplicationSiteDataStoreVersionId('zenko', 'b');
        assert.deepStrictEqual(md.getReplicationInfo().backends, [{
            site: 'zenko',
            status: 'PENDING',
            dataStoreVersionId: 'b',
        }]);
    });

    it('ObjectMD::getReplicationSiteDataStoreVersionId', () => {
        md.setReplicationInfo({
            backends: [{
                site: 'zenko',
                status: 'PENDING',
                dataStoreVersionId: 'a',
            }],
        });
        assert.strictEqual(
            md.getReplicationSiteDataStoreVersionId('zenko'), 'a');
    });

    it('ObjectMd::isMultipartUpload', () => {
        md.setContentMd5('68b329da9893e34099c7d8ad5cb9c940');
        assert.strictEqual(md.isMultipartUpload(), false);
        md.setContentMd5('741e0f4bad5b093044dc54a74d911094-1');
        assert.strictEqual(md.isMultipartUpload(), true);
        md.setContentMd5('bda0c0bed89c8bdb9e409df7ae7073c5-9876');
        assert.strictEqual(md.isMultipartUpload(), true);
    });

    it('ObjectMD::getUserMetadata', () => {
        md.setUserMetadata({
            'x-amz-meta-foo': 'bar',
            'x-amz-meta-baz': 'qux',
            // This one should be filtered out
            'x-amz-storage-class': 'STANDARD_IA',
            // This one should be changed to 'x-amz-meta-foobar'
            'x-ms-meta-foobar': 'bar',
            // ACLs are updated
            'acl': {
                FULL_CONTROL: ['john'],
            },
        });
        assert.deepStrictEqual(JSON.parse(md.getUserMetadata()), {
            'x-amz-meta-foo': 'bar',
            'x-amz-meta-baz': 'qux',
            'x-amz-meta-foobar': 'bar',
        });
        assert.deepStrictEqual(md.getAcl(), {
            FULL_CONTROL: ['john'],
        });
    });

    it('ObjectMD:clearMetadataValues', () => {
        md.setUserMetadata({
            'x-amz-meta-foo': 'bar',
        });
        md.clearMetadataValues();
        assert.strictEqual(md.getUserMetadata(), undefined);
    });

    it('ObjectMD::microVersionId unset', () => {
        assert.strictEqual(md.getMicroVersionId(), null);
    });

    it('ObjectMD::microVersionId set', () => {
        const generatedIds = new Set();
        for (let i = 0; i < 100; ++i) {
            md.updateMicroVersionId();
            generatedIds.add(md.getMicroVersionId());
        }
        // all generated IDs should be different
        assert.strictEqual(generatedIds.size, 100);
        generatedIds.forEach(key => {
            // length is always 16 in hex because leading 0s are
            // also encoded in the 8-byte random buffer.
            assert.strictEqual(key.length, 16);
        });
    });

    it('ObjectMD::set/getRetentionInfo', () => {
        md.setRetentionInfo({
            mode: 'COMPLIANCE',
            retainUntilDate: retainDate.toISOString(),
        });
        assert.deepStrictEqual(md.getRetentionInfo(), {
            mode: 'COMPLIANCE',
            retainUntilDate: retainDate.toISOString(),
        });
    });
});

describe('ObjectMD import from stored blob', () => {
    it('should export and import correctly the latest model version', () => {
        const md = new ObjectMD();
        const jsonMd = md.getSerialized();
        const importedRes = ObjectMD.createFromBlob(jsonMd);
        assert.ifError(importedRes.error);
        const importedMd = importedRes.result;
        assert.deepStrictEqual(md, importedMd);
    });

    it('should convert old location to new location', () => {
        const md = new ObjectMD();
        const value = md.getValue();
        value['md-model-version'] = 1;
        value.location = 'stringLocation';
        const jsonMd = JSON.stringify(value);
        const importedRes = ObjectMD.createFromBlob(jsonMd);
        assert.strictEqual(importedRes.error, undefined);
        const importedMd = importedRes.result;
        const valueImported = importedMd.getValue();
        assert.strictEqual(valueImported['md-model-version'],
                           constants.mdModelVersion);
        assert.deepStrictEqual(valueImported.location,
                               [{ key: 'stringLocation' }]);
    });

    it('should keep null location as is', () => {
        const md = new ObjectMD();
        const value = md.getValue();
        value.location = null;
        const jsonMd = JSON.stringify(value);
        const importedRes = ObjectMD.createFromBlob(jsonMd);
        assert.strictEqual(importedRes.error, undefined);
        const importedMd = importedRes.result;
        const valueImported = importedMd.getValue();
        assert.deepStrictEqual(valueImported.location, null);
        importedMd.setLocation([]);
        assert.deepStrictEqual(importedMd.getValue().location, null);
    });

    it('should add dataStoreName attribute if missing', () => {
        const md = new ObjectMD();
        const value = md.getValue();
        value['md-model-version'] = 2;
        delete value.dataStoreName;
        const jsonMd = JSON.stringify(value);
        const importedRes = ObjectMD.createFromBlob(jsonMd);
        assert.strictEqual(importedRes.error, undefined);
        const importedMd = importedRes.result;
        const valueImported = importedMd.getValue();
        assert.strictEqual(valueImported['md-model-version'],
                           constants.mdModelVersion);
        assert.notStrictEqual(valueImported.dataStoreName, undefined);
    });

    it('should return undefined for dataStoreVersionId if no object location',
    () => {
        const md = new ObjectMD();
        const value = md.getValue();
        const jsonMd = JSON.stringify(value);
        const importedRes = ObjectMD.createFromBlob(jsonMd);
        assert.strictEqual(importedRes.error, undefined);
        const importedMd = importedRes.result;
        assert.strictEqual(importedMd.getDataStoreVersionId(), undefined);
    });

    it('should get dataStoreVersionId if saved in object location', () => {
        const md = new ObjectMD();
        const dummyLocation = {
            dataStoreVersionId: 'data-store-version-id',
        };
        md.setLocation([dummyLocation]);
        const value = md.getValue();
        const jsonMd = JSON.stringify(value);
        const importedRes = ObjectMD.createFromBlob(jsonMd);
        assert.strictEqual(importedRes.error, undefined);
        const importedMd = importedRes.result;
        assert.strictEqual(importedMd.getDataStoreVersionId(),
            dummyLocation.dataStoreVersionId);
    });

    it('should return an error if blob is malformed JSON', () => {
        const importedRes = ObjectMD.createFromBlob('{BAD JSON}');
        assert.notStrictEqual(importedRes.error, undefined);
        assert.strictEqual(importedRes.result, undefined);
    });
});

describe('getAttributes static method', () => {
    it('should return object metadata attributes', () => {
        const attributes = ObjectMD.getAttributes();
        const expectedResult = {
            'owner-display-name': true,
            'owner-id': true,
            'cache-control': true,
            'content-disposition': true,
            'content-encoding': true,
            'expires': true,
            'content-length': true,
            'content-type': true,
            'content-md5': true,
            'content-language': true,
            'creation-time': true,
            'x-amz-version-id': true,
            'x-amz-server-version-id': true,
            'x-amz-storage-class': true,
            'x-amz-server-side-encryption': true,
            'x-amz-server-side-encryption-aws-kms-key-id': true,
            'x-amz-server-side-encryption-customer-algorithm': true,
            'x-amz-website-redirect-location': true,
            'acl': true,
            'key': true,
            'location': true,
            'azureInfo': true,
            'isNull': true,
            'nullVersionId': true,
            'isDeleteMarker': true,
            'versionId': true,
            'tags': true,
            'replicationInfo': true,
            'dataStoreName': true,
            'last-modified': true,
            'md-model-version': true };
        assert.deepStrictEqual(attributes, expectedResult);
    });
});
<|endoftext|>