Giriş
Yazılı Diziler, WebGL'de ikili verileri işlemek için etkili bir yönteme ihtiyaç duyulmasından doğan ve tarayıcılara nispeten yakın zamanda eklenen bir özelliktir. Yazılı Dizi, C'de dizilerin işleyiş şekline benzer şekilde, içinde yazılı bir görünüme sahip bir bellek parçasıdır. Türlendirilmiş diziler ham bellekle desteklendiğinden JavaScript motoru, verileri zahmetli bir şekilde yerel bir temsile dönüştürmek zorunda kalmadan belleği doğrudan yerel kitaplıklara iletebilir. Sonuç olarak, WebGL'ye ve ikili verilerle ilgilenen diğer API'lere veri aktarırken tescilli diziler JavaScript dizilerinden çok daha iyi performans gösterir.
Yazılı dizi görünümleri, ArrayBuffer segmenti için tek türlü diziler gibi davranır. Float32Array, Float64Array, Int32Array ve Uint8Array gibi kendi kendini açıklayan adlara sahip, tüm yaygın sayısal türler için görünümler vardır. Ayrıca, Canvas'ın ImageData özelliğindeki piksel dizisi türü yerine geçen özel bir görünüm de vardır: Uint8ClampedArray.
DataView, ikinci görünüm türüdür ve heterojen verileri işlemek için kullanılır. DataView nesnesi, dizi benzeri bir API yerine rastgele bayt ofsetlerinde rastgele veri türlerini okumak ve yazmak için bir get/set API'si sağlar. DataView, dosya üstbilgilerini ve bu tür yapı benzeri diğer verileri okumak ve yazmak için mükemmel bir seçimdir.
Yazılı dizileri kullanmayla ilgili temel bilgiler
Yazılı dizi görünümleri
Türelendirilmiş dizileri kullanmak için bir ArrayBuffer ve bu ArrayBuffer için bir görünüm oluşturmanız gerekir. En kolay yol, istediğiniz boyut ve türde bir dize dizisi görünümü oluşturmaktır.
// Typed array views work pretty much like normal arrays.
var f64a = new Float64Array(8);
f64a[0] = 10;
f64a[1] = 20;
f64a[2] = f64a[0] + f64a[1];
Çeşitli türde dizi görünümleri vardır. Hepsi aynı API'yi kullandığından, bir API'yi nasıl kullanacağınızı öğrendikten sonra hemen hemen hepsini kullanabilirsiniz. Sonraki örnekte, şu anda mevcut olan her bir türde dizi görünümünden bir tane oluşturacağım.
// Floating point arrays.
var f64 = new Float64Array(8);
var f32 = new Float32Array(16);
// Signed integer arrays.
var i32 = new Int32Array(16);
var i16 = new Int16Array(32);
var i8 = new Int8Array(64);
// Unsigned integer arrays.
var u32 = new Uint32Array(16);
var u16 = new Uint16Array(32);
var u8 = new Uint8Array(64);
var pixels = new Uint8ClampedArray(64);
Sonuncu biraz özeldir, giriş değerlerini 0 ile 255 arasında sıkıştırır. Bu özellik, 8 bit aralığının taşmasını önlemek için artık resim işleme matematik işlemlerinizi manuel olarak sınırlamanız gerekmediğinden özellikle Canvas resim işleme algoritmaları için kullanışlıdır.
Örneğin, Uint8Array içinde depolanan bir resme gamma faktörü uygulamak için aşağıdakileri yapmanız gerekir. Pek güzel değil:
u8[i] = Math.min(255, Math.max(0, u8[i] * gamma));
Uint8ClampedArray ile manuel sınırlamayı atlayabilirsiniz:
pixels[i] *= gamma;
Yazılı dizi görünümleri oluşturmanın diğer yolu, önce bir ArrayBuffer oluşturmak ve ardından onu işaret eden görünümler oluşturmaktır. Size harici veriler sağlayan API'ler genellikle ArrayBuffer'larla çalışır. Bu nedenle, bu API'lere yazılmış bir dizi görünümü bu şekilde alırsınız.
var ab = new ArrayBuffer(256); // 256-byte ArrayBuffer.
var faFull = new Uint8Array(ab);
var faFirstHalf = new Uint8Array(ab, 0, 128);
var faThirdQuarter = new Uint8Array(ab, 128, 64);
var faRest = new Uint8Array(ab, 192);
Aynı ArrayBuffer'da birden fazla görünüm de olabilir.
var fa = new Float32Array(64);
var ba = new Uint8Array(fa.buffer, 0, Float32Array.BYTES_PER_ELEMENT); // First float of fa.
Yazılı bir diziyi başka bir yazılı diziye kopyalamanın en hızlı yolu, yazılı dizi ayarlama yöntemini kullanmaktır. memcpy benzeri bir kullanım için görünümlerin arabelleklerine Uint8Arrays oluşturun ve verileri kopyalamak için set'i kullanın.
function memcpy(dst, dstOffset, src, srcOffset, length) {
var dstU8 = new Uint8Array(dst, dstOffset, length);
var srcU8 = new Uint8Array(src, srcOffset, length);
dstU8.set(srcU8);
};
DataView
Çeşitli türlerde veriler içeren ArrayBuffer'ları kullanmanın en kolay yolu, arabelleğe bir DataView kullanmaktır. 8 bitlik imzalanmamış bir int içeren bir başlık, ardından iki 16 bitlik int ve ardından 32 bitlik kayan nokta içeren bir yükü dizisi içeren bir dosya biçimimiz olduğunu varsayalım. Bu verileri, yazılı dizi görünümleriyle geri okumak mümkün olsa da biraz zahmetli bir işlemdir. DataView ile başlığı okuyabilir ve float dizisi için bir türlenmiş dizi görünümü kullanabiliriz.
var dv = new DataView(buffer);
var vector_length = dv.getUint8(0);
var width = dv.getUint16(1); // 0+uint8 = 1 bytes offset
var height = dv.getUint16(3); // 0+uint8+uint16 = 3 bytes offset
var vectors = new Float32Array(width*height*vector_length);
for (var i=0, off=5; i<vectors.length; i++, off+=4) {
vectors[i] = dv.getFloat32(off);
}
Yukarıdaki örnekte, okuduğum tüm değerler büyük endian'dır. Arabellekteki değerler küçük endian ise isteğe bağlı littleEndian parametresini alıcıya iletebilirsiniz:
...
var width = dv.getUint16(1, true);
var height = dv.getUint16(3, true);
...
vectors[i] = dv.getFloat32(off, true);
...
Yazılı dizi görünümlerinin her zaman yerel bayt düzeninde olduğunu unutmayın. Bu, onları hızlı yapmak içindir. Büyük/küçük endianlık sorununun yaşanacağı verileri okumak ve yazmak için bir DataView kullanmanız gerekir.
Veri Görünümü, tamponlara değer yazmak için yöntemlere de sahiptir. Bu ayarlayıcılar, alıcılarla aynı şekilde adlandırılır. "set" ve ardından veri türü gelir.
dv.setInt32(0, 25, false); // set big-endian int32 at byte offset 0 to 25
dv.setInt32(4, 25); // set big-endian int32 at byte offset 4 to 25
dv.setFloat32(8, 2.5, true); // set little-endian float32 at byte offset 8 to 2.5
endianness hakkında bir tartışma
Bit sırası veya bayt sırası, çok baytlı sayıların bilgisayarın belleğinde depolanma sırasıdır. Büyük endian terimi, en önemli baytı önce depolayan bir CPU mimarisini, küçük endian ise en az önemli baytı önce depolayan bir mimariyi tanımlar. Belirli bir CPU mimarisinde hangi büyük endianlığın kullanılacağı tamamen keyfidir. Her ikisini seçmek için iyi nedenler vardır. Hatta bazı CPU'lar hem büyük endian hem de küçük endian verileri destekleyecek şekilde yapılandırılabilir.
Neden büyük endianlık konusunda endişelenmelisiniz? Bunun nedeni basit. Diskten veya ağdan veri okurken ya da yazarken verilerin büyük endian veya küçük endian olması gerekir. Bu sayede, verilerle çalışan CPU'nun büyük endian veya küçük endian olması fark etmeksizin verilerin doğru şekilde yorumlanması sağlanır. Ağ bağlantılarının giderek arttığı dünyamızda, sunuculardan veya ağdaki diğer eşlerden gelen ikili verilerle çalışması gerekebilecek her türlü cihazı (büyük veya küçük endian) düzgün bir şekilde desteklemek çok önemlidir.
DataView arayüzü, özellikle dosyalara ve ağdan veri okumak ve yazmak için tasarlanmıştır. DataView, belirtilen bir büyüklük sırasına sahip veriler üzerinde çalışır. Büyük veya küçük olmak üzere büyüklük, her bir değere her erişimde belirtilmelidir. Bu, tarayıcının çalıştığı CPU'nun büyüklük özelliği ne olursa olsun, ikili verileri okurken veya yazarken tutarlı ve doğru sonuçlar almanızı sağlar.
Uygulamanız bir sunucudan ikili verileri okuduğunda, bunları uygulamanızın dahili olarak kullandığı veri yapılarına dönüştürmek için bir kez taramanız gerekir. Bu aşamada DataView kullanılmalıdır. Yazılım türüne göre düzenlenmiş dizi görünümleri, CPU'nun doğal büyük endianlığını kullandığından, çok baytlı yazılım türüne göre düzenlenmiş dizi görünümlerini (Int16Array, Uint16Array vb.) doğrudan XMLHttpRequest, FileReader veya başka bir giriş/çıkış API'si aracılığıyla alınan verilerle kullanmak iyi bir fikir değildir. Bu konuyla ilgili daha fazla bilgiyi aşağıda bulabilirsiniz.
Birkaç basit örneğe göz atalım. Windows BMP dosya biçimi, Windows'un ilk günlerinde resim depolamak için kullanılan standart biçimdi. Yukarıda bağlantısı verilen dokümanda, dosyadaki tüm tam sayı değerlerinin küçük endian biçiminde depolandığı açıkça belirtilmektedir. Bu makaleye eşlik eden DataStream.js kitaplığını kullanarak BMP üstbilgisinin başlangıcını ayrıştıran bir kod snippet'ini aşağıda bulabilirsiniz:
function parseBMP(arrayBuffer) {
var stream = new DataStream(arrayBuffer, 0,
DataStream.LITTLE_ENDIAN);
var header = stream.readUint8Array(2);
var fileSize = stream.readUint32();
// Skip the next two 16-bit integers
stream.readUint16();
stream.readUint16();
var pixelOffset = stream.readUint32();
// Now parse the DIB header
var dibHeaderSize = stream.readUint32();
var imageWidth = stream.readInt32();
var imageHeight = stream.readInt32();
// ...
}
Aşağıda, WebGL örnekleri projesindeki Yüksek Dinamik Aralık oluşturma demosundan bir örnek verilmiştir. Bu demo, yüksek dinamik aralıktaki dokuları temsil eden ham, küçük endian kayan nokta verilerini indirir ve WebGL'ye yüklemesi gerekir. Tüm CPU mimarilerindeki kayan nokta değerlerini doğru şekilde yorumlayan kod snippet'ini aşağıda bulabilirsiniz. "arrayBuffer" değişkeninin, XMLHttpRequest aracılığıyla sunucudan yeni indirilmiş bir ArrayBuffer olduğunu varsayalım:
var arrayBuffer = ...;
var data = new DataView(arrayBuffer);
var tempArray = new Float32Array(
data.byteLength / Float32Array.BYTES_PER_ELEMENT);
var len = tempArray.length;
// Incoming data is raw floating point values
// with little-endian byte ordering.
for (var jj = 0; jj < len; ++jj) {
tempArray[jj] =
data.getFloat32(jj * Float32Array.BYTES_PER_ELEMENT, true);
}
gl.texImage2D(...other arguments...,
gl.RGB, gl.FLOAT, tempArray);
Genel kural şudur: Web sunucusundan ikili veriler aldıktan sonra, bir DataView ile bu verileri bir kez tarayın. Ayrı ayrı sayısal değerleri okuyup bunları başka bir veri yapısında (az miktarda yapılandırılmış veri için JavaScript nesnesi veya büyük veri blokları için yazılmış dizi görünümü) depolayın. Bu, kodunuzun her tür CPU'da düzgün çalışmasını sağlar. Verileri bir dosyaya veya ağa yazmak için DataView'ı da kullanın ve oluşturduğunuz veya kullandığınız dosya biçimini oluşturmak için çeşitli set yöntemlerine littleEndian bağımsız değişkenini uygun şekilde belirttiğinizden emin olun.
Ağ üzerinden aktarılan tüm verilerin (en azından çok baytlı değerler için) dolaylı olarak bir biçimi ve bir büyük/küçük endianlığı olduğunu unutmayın. Uygulamanızın ağ üzerinden gönderdiği tüm verilerin biçimini açıkça tanımladığınızdan ve belgelediğinizden emin olun.
Yazılı dizileri kullanan tarayıcı API'leri
Şu anda Typed Array kullanan farklı tarayıcı API'lerine kısaca göz atacağız. Şu anda WebGL, Canvas, Web Audio API, XMLHttpRequest'ler, WebSocket'ler, Web İşleyicileri, Media Source API ve Dosya API'leri bu kapsamdadır. API listesinde, Typed Arrays'ın performansa duyarlı multimedya çalışmaları için ve verileri verimli bir şekilde aktarmak için uygun olduğunu görebilirsiniz.
WebGL
Türlü diziler ilk olarak WebGL'de kullanıldı. Burada, arabellek verileri ve resim verileri aktarmak için kullanılır. Bir WebGL arabellek nesnesinin içeriğini ayarlamak için gl.bufferData() çağrısını bir Typed Array ile kullanırsınız.
var floatArray = new Float32Array([1,2,3,4,5,6,7,8]);
gl.bufferData(gl.ARRAY_BUFFER, floatArray);
Türelendirilmiş diziler, doku verilerini iletmek için de kullanılır. Türlü dizi kullanarak doku içeriğini iletmek için temel bir örnek verilmiştir.
var pixels = new Uint8Array(16*16*4); // 16x16 RGBA image
gl.texImage2D(
gl.TEXTURE_2D, // target
0, // mip level
gl.RGBA, // internal format
16, 16, // width and height
0, // border
gl.RGBA, //format
gl.UNSIGNED_BYTE, // type
pixels // texture data
);
WebGL bağlamındaki pikselleri okumak için Typed Arrays'e de ihtiyacınız vardır.
var pixels = new Uint8Array(320*240*4); // 320x240 RGBA image
gl.readPixels(0, 0, 320, 240, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
Canvas 2D
Canvas ImageData nesnesi kısa süre önce Typed Arrays spesifikasyonuyla çalışacak şekilde değiştirildi. Artık bir kanvas öğesindeki piksellerin Typed Arrays temsilini alabilirsiniz. Artık tuval öğesiyle uğraşmak zorunda kalmadan tuval piksel dizileri de oluşturabileceğiniz ve düzenleyebileceğiniz için bu özellik kullanışlıdır.
var imageData = ctx.getImageData(0,0, 200, 100);
var typedArray = imageData.data // data is a Uint8ClampedArray
XMLHttpRequest2
XMLHttpRequest, Typed Array desteği aldı. Artık bir JavaScript dizesini Typed Array olarak ayrıştırmak yerine Typed Array yanıtı alabilirsiniz. Bu, getirilen verileri doğrudan multimedya API'lerine iletmek ve ağdan getirilen ikili dosyaları ayrıştırmak için gerçekten kullanışlıdır.
Tek yapmanız gereken XMLHttpRequest nesnesinin responseType özelliğini "arraybuffer" olarak ayarlamaktır.
xhr.responseType = 'arraybuffer';
Ağdan veri indirirken büyük/küçük endianlık sorunlarına dikkat etmeniz gerektiğini unutmayın. Yukarıdaki büyük/küçük endianlık bölümüne bakın.
Dosya API'leri
FileReader, dosya içeriklerini ArrayBuffer olarak okuyabilir. Ardından, içeriğini değiştirmek için arabelleğe yazılmış dizi görünümleri ve veri görünümleri ekleyebilirsiniz.
reader.readAsArrayBuffer(file);
Burada da büyük/küçük endianlığı göz önünde bulundurmanız gerekir. Ayrıntılar için büyük endianlık bölümüne göz atın.
Aktarılabilir nesneler
postMessage'deki aktarılabilir nesneler, ikili verileri diğer pencerelere ve Web İşleyicilere çok daha hızlı aktarmanızı sağlar. Bir nesneyi aktarılabilir olarak bir İşçi'ye gönderdiğinizde, gönderen ileti dizisinde nesneye erişilemez hale gelir ve alıcı İşçi nesnenin sahibi olur. Bu sayede, gönderilen verilerin kopyalanmadığı, yalnızca türü belirtilmiş dizinin sahipliğinin alıcıya aktarıldığı son derece optimize edilmiş bir uygulama elde edilir.
Aktarıma uygun nesneleri Web İşleyiciler ile kullanmak için işleyicide webkitPostMessage yöntemini kullanmanız gerekir. webkitPostMessage yöntemi, postMessage ile aynı şekilde çalışır ancak bir yerine iki bağımsız değişken alır. Eklenen ikinci bağımsız değişken, işçiye aktarmak istediğiniz bir nesne dizisidir.
worker.webkitPostMessage(oneGBTypedArray, [oneGBTypedArray]);
Çalışan, nesneleri çalışandan geri almak için aynı şekilde ana iş parçacığına geri aktarabilir.
webkitPostMessage({results: grand, youCanHaveThisBack: oneGBTypedArray}, [oneGBTypedArray]);
Sıfır kopya, woo!
Media Source API
Kısa süre önce medya öğeleri, Media Source API biçiminde Typed Array özelliklerinden de yararlanmaya başladı. webkitSourceAppend işlevini kullanarak video verileri içeren bir Typed Array öğesini doğrudan bir video öğesine iletebilirsiniz. Bu sayede video öğesi, video verilerini mevcut videonun sonuna ekler. SourceAppend, geçiş reklamları, oynatma listeleri, akış ve tek bir video öğesi kullanarak birden fazla video oynatmak isteyebileceğiniz diğer kullanım alanları için mükemmeldir.
video.webkitSourceAppend(uint8Array);
İkili WebSocket'ler
Tüm verilerinizi dize haline getirmeniz gerekmemesi için WebSocket'lerle birlikte Typed Arrays'i de kullanabilirsiniz. Etkili protokoller yazmak ve ağ trafiğini en aza indirmek için idealdir.
socket.binaryType = 'arraybuffer';
Sevindim! API incelemesi tamamlandı. Tanımlanmış dizileri işlemek için üçüncü taraf kitaplıklarına göz atalım.
Üçüncü taraf kitaplıkları
jDataView
jDataView, tüm tarayıcılar için bir DataView shim'i uygular. DataView eskiden yalnızca WebKit'te desteklenen bir özellikti ancak artık diğer tarayıcıların çoğu tarafından desteklenmektedir. Mozilla geliştirici ekibi, DataView'ı Firefox'ta da etkinleştirecek bir düzeltme yayınlama sürecindedir.
Chrome Geliştirici İlişkileri Ekibi'nden Eric Bidelman, jDataView kullanan küçük bir MP3 ID3 etiketi okuyucu örneği yazdı. Blog yayınındaki kullanım örneğini aşağıda bulabilirsiniz:
var dv = new jDataView(arraybuffer);
// "TAG" starts at byte -128 from EOF.
// See http://en.wikipedia.org/wiki/ID3
if (dv.getString(3, dv.byteLength - 128) == 'TAG') {
var title = dv.getString(30, dv.tell());
var artist = dv.getString(30, dv.tell());
var album = dv.getString(30, dv.tell());
var year = dv.getString(4, dv.tell());
} else {
// no ID3v1 data found.
}
stringencoding
Yazılı dizilerde dizelerle çalışmak şu anda biraz zor olsa da bu konuda yardımcı olan stringencoding kitaplığı vardır. Stringencoding, önerilen Typed Array dize kodlama spesifikasyonunu uygular. Bu nedenle, gelecekte neler olacağına dair fikir edinmek için de iyi bir yöntemdir.
stringencoding işlevinin temel kullanım örneğini aşağıda bulabilirsiniz:
var uint8array = new TextEncoder(encoding).encode(string);
var string = new TextDecoder(encoding).decode(uint8array);
BitView.js
BitView.js adlı, Typed Arrays için küçük bir bit değiştirme kitaplığı yazdım. Adından da anlaşılacağı gibi, BitView, bitlerle çalıştığı için DataView'a çok benzer şekilde çalışır. BitView ile bir ArrayBuffer'daki belirli bir bit ofsetindeki bitin değerini alıp ayarlayabilirsiniz. BitView, 6 bit ve 12 bit int'leri rastgele bit ofsetlerinde depolamak ve yüklemek için yöntemlere de sahiptir.
Ekranlar daha uzun boyut boyunca 4096'dan daha az piksele sahip olduğundan 12 bitlik tam sayılar, ekran koordinatlarıyla çalışmak için idealdir. 32 bit int yerine 12 bit int kullanarak boyutu% 62 oranında azaltabilirsiniz. Daha uç bir örnek vermek gerekirse, koordinatlar için 64 bit kayan nokta kullanan Shapefile'lerle çalışıyordum ancak model yalnızca ekran boyutunda gösterileceği için bu hassasiyete ihtiyacım yoktu. Önceki koordinattaki değişiklikleri kodlamak için 6 bitlik deltalarla 12 bitlik temel koordinatlara geçiş, dosya boyutunu onda birine düşürdü. Demoyu burada bulabilirsiniz.
BitView.js'i kullanma örneğini aşağıda bulabilirsiniz:
var bv = new BitView(arrayBuffer);
bv.setBit(4, 1); // Set fourth bit of arrayBuffer to 1.
bv.getBit(17); // Get 17th bit of arrayBuffer.
bv.getBit(50*8 + 3); // Get third bit of 50th byte in arrayBuffer.
bv.setInt6(3, 18); // Write 18 as a 6-bit int to bit position 3 in arrayBuffer.
bv.getInt12(9); // Read a 12-bit int from bit position 9 in arrayBuffer.
DataStream.js
Yazılı dizilerle ilgili en heyecan verici özelliklerden biri, JavaScript'te ikili dosyalarla çalışmayı kolaylaştırmasıdır. Artık bir dizeyi karakter karakter ayrıştırmak ve karakterleri manuel olarak ikili sayılara dönüştürmek yerine XMLHttpRequest ile bir ArrayBuffer alabilir ve bunu doğrudan bir DataView kullanarak işleyebilirsiniz. Bu sayede, örneğin bir MP3 dosyasını yükleyebilir ve ses çalarınızda kullanmak için meta veri etiketlerini okuyabilirsiniz. Dilerseniz bir şekil dosyasını yükleyip WebGL modeline dönüştürebilirsiniz. Dilerseniz JPEG'deki EXIF etiketlerini okuyup slayt gösterisi uygulamanızda gösterebilirsiniz.
ArrayBuffer XHR'lerle ilgili sorun, arabellekten yapı benzeri verileri okumanın biraz can sıkıcı olmasıdır. DataView, bir seferde birkaç sayıyı endian uyumlu bir şekilde okumak için iyidir. Yazılı dizi görünümleri ise öğe boyutuna göre hizalanmış yerel endian sayı dizilerini okumak için iyidir. Veri dizilerini ve yapılarını, endian'a uygun bir şekilde okumanın eksik olduğunu düşünüyorduk. DataStream.js dosyasını girin.
DataStream.js, ArrayBuffer'lardan skaler, dize, dizi ve veri yapılarını dosya benzeri bir şekilde okuyup yazan bir Typed Arrays kitaplığıdır.
ArrayBuffer'dan bir kayan nokta dizisi okuma örneği:
// without DataStream.js
var dv = new DataView(buffer);
var f32 = new Float32Array(buffer.byteLength / 4);
var littleEndian = true;
for (var i = 0; i<f32.length; i++) {
f32[i] = dv.getFloat32(i*4, littleEndian);
}
// with DataStream.js
var ds = new DataStream(buffer);
ds.endianness = DataStream.LITTLE_ENDIAN;
var f32 = ds.readFloat32Array(ds.byteLength / 4);
DataStream.js'in gerçekten yararlı olduğu yer, daha karmaşık verileri okumaktır. JPEG işaretçilerini okuyan bir yönteminiz olduğunu varsayalım:
// without DataStream.js
var dv = new DataView(buffer);
var objs = [];
for (var i=0; i<buffer.byteLength;) {
var obj = {};
obj.tag = dv.getUint16(i);
i += 2;
obj.length = dv.getUint16(i);
i += 2;
obj.data = new Uint8Array(obj.length - 2);
for (var j=0; j<obj.data.length; j++,i++) {
obj.data[j] = dv.getUint8(i);
}
objs.push(obj);
}
// with DataStream.js
var ds = new DataStream(buffer);
ds.endianness = ds.BIG_ENDIAN;
var objs = [];
while (!ds.isEof()) {
var obj = {};
obj.tag = ds.readUint16();
obj.length = ds.readUint16();
obj.data = ds.readUint8Array(obj.length - 2);
objs.push(obj);
}
Veri yapılarını okumak için DataStream.readStruct yöntemini de kullanabilirsiniz. readStruct yöntemi, struct üyelerinin türlerini içeren bir struct tanımı dizisi alır. Karmaşık türleri işlemek için geri çağırma işlevleri vardır ve veri dizilerini ve iç içe yerleştirilmiş yapıları da işler:
// with DataStream.readStruct
ds.readStruct([
'objs', ['[]', [ // objs: array of tag,length,data structs
'tag', 'uint16',
'length', 'uint16',
'data', ['[]', 'uint8', function(s,ds){ return s.length - 2; }], // get length with a function
'*'] // read in as many struct as there are
]);
Gördüğünüz gibi yapı tanımı, [ad, tür] çiftlerinden oluşan düz bir dizidir. İç içe yerleştirilmiş yapıların oluşturulması için türe ait bir dizi gerekir. Diziler, ikinci öğenin dizi öğesi türü ve üçüncü öğenin dizi uzunluğu (sayı, daha önce okunan alana referans veya geri çağırma işlevi olarak) olduğu üç öğeli bir dizi kullanılarak tanımlanır. Dizi tanımının ilk öğesi kullanılmamaktadır.
Tür için olası değerler şunlardır:
Number types
Unsuffixed number types use DataStream endianness.
To explicitly specify endianness, suffix the type with
'le' for little-endian or 'be' for big-endian,
e.g. 'int32be' for big-endian int32.
'uint8' -- 8-bit unsigned int
'uint16' -- 16-bit unsigned int
'uint32' -- 32-bit unsigned int
'int8' -- 8-bit int
'int16' -- 16-bit int
'int32' -- 32-bit int
'float32' -- 32-bit float
'float64' -- 64-bit float
String types
'cstring' -- ASCII string terminated by a zero byte.
'string:N' -- ASCII string of length N.
'string,CHARSET:N' -- String of byteLength N encoded with given CHARSET.
'u16string:N' -- UCS-2 string of length N in DataStream endianness.
'u16stringle:N' -- UCS-2 string of length N in little-endian.
'u16stringbe:N' -- UCS-2 string of length N in big-endian.
Complex types
[name, type, name_2, type_2, ..., name_N, type_N] -- Struct
function(dataStream, struct) {} -- Callback function to read and return data.
{get: function(dataStream, struct) {}, set: function(dataStream, struct) {}}
-- Getter/setter functions to reading and writing data. Handy for using the
same struct definition for both reading and writing.
['', type, length] -- Array of given type and length. The length can be either
a number, a string that references a previously-read
field, or a callback function(struct, dataStream, type){}.
If length is set to '*', elements are read from the
DataStream until a read fails.
JPEG meta verilerini okumayla ilgili canlı bir örneği burada görebilirsiniz. Demoda, JPEG dosyasının etiket düzeyindeki yapısını okumak için DataStream.js (bazı EXIF ayrıştırma işlemleriyle birlikte) ve JPEG resminin kodunu çözmek ve JavaScript'te görüntülemek için jpg.js kullanılmaktadır.
Yazılı Dizilerin Tarihçesi
Yazılı diziler, JavaScript dizilerinin grafik sürücüsüne aktarılmasının performans sorunlarına neden olduğunu fark ettiğimiz WebGL'in erken uygulama aşamasında kullanıma sunulmuştur. JavaScript dizilerinde, WebGL bağlamasının bir yerel dizi ayırması ve JavaScript dizisini tarayarak ve dizideki her JavaScript nesnesini gerekli yerel türe dönüştürerek bu diziyi doldurması gerekiyordu.
Veri dönüştürme darboğazını düzeltmek için Mozilla'dan Vladimir Vukicevic, JavaScript arayüzü olan C tarzı bir kayan nokta dizisi olan CanvasFloatArray'ı yazdı. Artık CanvasFloatArray'ı JavaScript'te düzenleyebilir ve bağlama işleminde ek bir işlem yapmanıza gerek kalmadan doğrudan WebGL'ye iletebilirsiniz. Daha sonraki iterasyonlarda CanvasFloatArray, WebGLFloatArray olarak yeniden adlandırıldı. Bu sınıf daha sonra Float32Array olarak yeniden adlandırıldı ve tampona erişmek için bir destekleyici ArrayBuffer ile türü belirtilmiş Float32Array görünümüne bölündü. Diğer tam sayı ve kayan nokta boyutları ile işaretli/işaretsiz varyantlar için de türler eklendi.
Tasarımla ilgili dikkat edilmesi gereken noktalar
Türlü dizilerin tasarımı, başlangıçta ikili verilerin yerel kitaplıklara verimli bir şekilde aktarılması ihtiyacından kaynaklanıyordu. Bu nedenle, türü belirtilmiş dizi görünümleri, ana makine CPU'sunun yerel endian düzenindeki hizalanmış veriler üzerinde çalışır. Bu kararlar, JavaScript'in grafik kartına köşe noktası verileri gönderme gibi işlemler sırasında maksimum performansa ulaşmasını sağlar.
DataView, verilerin her zaman belirli bir büyüklük sırasına sahip olduğu ve maksimum performans için hizalanmadığı dosya ve ağ G/Ç işlemleri için özel olarak tasarlanmıştır.
Bellek içi veri derleme (tanımlanmış dizi görünümleri kullanılarak) ve G/Ç (DataView kullanılarak) arasında yapılan tasarım ayrımı bilinçli bir karardı. Modern JavaScript motorları, yazılmış dizi görünümlerini büyük ölçüde optimize eder ve bunlarla sayısal işlemlerde yüksek performans elde eder. Yazılı dizi görünümlerinin mevcut performans düzeyleri bu tasarım kararı sayesinde mümkün olmuştur.