Yazılan diziler - Tarayıcıdaki ikili program verileri

Ilmari Heikkinen

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, yazılan diziler, verileri WebGL'ye ve ikili verilerle ilgilenen diğer API'lere iletme açısından 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 genel sayısal türlere ilişkin 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şlemeye yöneliktir. 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 başlıklarını ve buna benzer diğer struct benzeri verileri okuyup yazmak için mükemmel bir şekilde çalışır.

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 paylaşır. Böylece, API'yi kullanmayı öğrendikten sonra hepsini nasıl kullanacağınızı neredeyse hiç bilemezsiniz. Bir 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'de depolanan bir resme gama faktörünü nasıl uygulayacağınız aşağıda açıklanmıştır. 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'a birden fazla görünüm de sahip olabilirsiniz.

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

Heterojen türlere sahip veriler içeren ArrayBuffers kullanmanın en kolay yolu, arabellekte 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 kayan dizi için yazılan dizi görünümünü 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ı hale getirmek 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

Endianlık 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ı belirtilmelidir. Bu, verilerin kendisiyle çalışan CPU'nun nihai yapısına bakılmaksızın doğru şekilde yorumlanmasını sağlar. Ağların her geçen gün daha da yayıldığı dünyamızda, sunuculardan veya ağdaki diğer eşlerden gelen ikili verilerle çalışması gerekebilecek, büyük veya küçük her türlü cihazı doğru bir şekilde desteklemek hayati önem taşır.

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 genellikle bir kez taramanız gerekir. Bu aşamada DataView kullanılmalıdır. Yazılı dizi görünümleri, CPU'nun doğal endianlığını kullandığından, çok baytlı yazılı 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 resimleri depolamak için standart biçim olarak kullanılıyordu. 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ıklı dokuları temsil eden ham, küçük endian kayan nokta verilerini indirir ve WebGL'ye yüklemesi gerekir. Tüm CPU mimarilerinde kayan nokta değerlerini doğru şekilde yorumlayan kod snippet'ini burada 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, web sunucusundan ikili veriler alındıktan sonra DataView ile bunların üzerinden tek bir geçiş yapılmasıdır. 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ılan Dizileri kullanan tarayıcı API'leri

Şu anda Türü Oluşturulan Dizileri kullanan farklı tarayıcı API'lerine dair kısa bir genel bakış yapacağım. Mevcut kırpmada WebGL, Canvas, Web Audio API, XMLHttpRequests, WebSockets, Web Workers, Media Source API ve File API'leri bulunuyor. API listesinde, Typed Arrays'ın performansa duyarlı multimedya çalışmaları ve verilerin verimli bir şekilde aktarılması için ideal 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 Typed Array ile gl.bufferData() çağrısını 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ından pikselleri okumak için Yazılan Diziler de gereklidir.

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 oluşturup düzenleyebileceğinizden bu yararlı olur.

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 değerini "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, endianus ile ilgili bölüme bakın.

Dosya API'leri

FileReader, dosya içeriklerini ArrayBuffer olarak okuyabilir. Ardından, arabelleğe yazılan dizi görünümleri ve DataView'ları ekleyerek içeriğini değiştirebilirsiniz.

reader.readAsArrayBuffer(file);

Endianlüğü de göz önünde bulundurmalısınız. 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 Çalışana Aktarılabilir olarak nesne gönderdiğinizde, nesne, gönderen ileti dizisinde erişilemez hale gelir ve alıcı çalışan nesnenin sahipliğini alır. 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!

Medya Kaynağı API'sı

Kısa süre önce medya öğeleri, Media Source API biçiminde bazı Typed Array özelliklerine de kavuştu. 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. Verimli protokoller yazmak ve ağ trafiğini en aza indirmek için idealdir.

socket.binaryType = 'arraybuffer';

Sevindim! Böylece API incelemesi tamamlanmış oldu. 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 kullanılabilen bir özellikti ancak artık diğer tarayıcıların çoğu tarafından desteklenmektedir. Mozilla geliştirici ekibi, Firefox'ta da DataView'ın etkinleştirilmesi için bir yama oluşturma 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ından bir kullanım örneği aşağıda verilmiştir:

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

Türü Oluşturulan Dizilerde dizelerle çalışmak, şu anda biraz zor bir iş olsa da dize kodlama kitaplığı bu konuda size yardımcı olabilir. Stringencoding, önerilen Typed Array dize kodlama spesifikasyonunu uygular. Bu nedenle, gelecekte neler olacağına dair fikir edinmek için de iyi bir yöntemdir.

Dize kodlamasının temel bir 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ı Yazılmış Diziler için küçük bir düzenleme kitaplığı yazdım. Adından da anlaşılacağı gibi, BitView, bitlerle çalıştığı için DataView'a çok benzer şekilde çalışır. BitView ile ArrayBuffer'da belirli bir bit ofsetindeki bir bitin değerini alıp ayarlayabilirsiniz. BitView, rastgele bit ofsetlerinde 6 bit ve 12 bit int'lerin depolanması ve yüklenmesi için de yöntemler sunar.

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ş yaparak dosya boyutunu onda birine düşürdük. 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&#39;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, örneğin bir MP3 dosyası yüklemeyi ve ses çalarınızda kullanılmak üzere meta veri etiketlerini okumayı kolaylaştırır. Veya bir şekil dosyası yükleyin ve bunu bir WebGL modeline dönüştürün. Alternatif olarak, JPEG biçimindeki EXIF etiketlerini okuyabilir ve bunları slayt gösterisi uygulamanızda gösterebilirsiniz.

ArrayBuffer XHR'leriyle 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 uyumlu bir şekilde okumanın kolay bir yolunun eksik olduğunu düşünüyorduk. DataStream.js dosyasını girin.

DataStream.js, ArrayBuffers'daki verileri 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);
}

Alternatif olarak, veri yapılarını okumak için DataStream.readStruct yöntemini 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. Demo, JPEG dosyasının etiket düzeyindeki yapısını okumak için DataStream.js'yi (bazı EXIF ayrıştırmalarıyla birlikte) ve JPEG resminin kodunu çözmek ve JavaScript'te görüntülemek için jpg.js'yi kullanmaktadı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 sorununu gidermek için Mozilla'dan Vladimir Vukicevic, JavaScript arayüze sahip C stili bir kayan dizi olan CanvasfloatArray'ı kaleme aldı. 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. Sonraki iterasyonlarda, CanvasfloatArray'in adı WebGLfloatArray olarak değiştirilmiştir. Bu dizi de float32Array olarak yeniden adlandırılıp arabelleğe erişmek için yedek ArrayBuffer ve yazılan float32Array-görünümüne bölünecektir. 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ıçtan itibaren ikili verilerin yerel kitaplıklara verimli bir şekilde aktarılması ihtiyacından kaynaklanıyordu. Bu nedenle, yazılan dizi görünümleri ana makine CPU'sunun yerel endianlığı'ndaki uyumlu 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 derlemesi (yazılan dizi görünümleri kullanılarak) ve G/Ç (DataView kullanılarak) arasında bölünen tasarım, bilinçli bir tasarımdı. 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ılan dizi görünümlerinin mevcut performans düzeyleri, bu tasarım kararıyla mümkün olmuştur.

Referanslar