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

Ilmari Heikkinen

Giriş

Yazılan Diziler, WebGL'de ikili verileri işlemek için etkili bir yönteme sahip olma ihtiyacından doğmuş, tarayıcılara göre yeni eklenen bir özelliktir. Yazılan dizi, C'deki dizilerin çalışma şekline benzer şekilde, yazılan bir görünümün bulunduğu bellek yığınıdır. Yazılan bir Dizi ham bellek tarafından desteklendiğinden, JavaScript motoru, verileri büyük bir zahmetle yerel gösterime dönüştürmek zorunda kalmadan belleği doğrudan yerel kitaplıklara geçirebilir. Sonuç olarak, yazılan diziler, verileri WebGL'ye ve ikili verilerle çalışan diğer API'lere geçirmekte JavaScript dizilerinden çok daha iyi performans gösterir.

Yazılan dizi görünümleri, bir ArrayBuffer segmentinin tek tür dizileri gibi davranır. Float32Array, Float64Array, Int32Array ve Uint8Array gibi kendini tanımlayan adlara sahip tüm normal sayısal türler için görünümler vardır. Tuval'in ImageData öğesindeki piksel dizisi türünün yerini alan özel bir görünüm de vardır: Uint8ClampedArray.

İkinci görünüm türü olan DataView, heterojen verileri işlemek için tasarlanmıştır. Dizi benzeri bir API'ye sahip olmak yerine DataView nesnesi, rastgele bayt ofsetlerinde rastgele veri türlerini okuyup yazmanız için bir get/set API'si sağlar. DataView, dosya başlıklarını ve yapı benzeri diğer verileri okuyup yazmak için idealdir.

Yazılan Dizileri kullanmaya ilişkin temel bilgiler

Yazılan dizi görünümleri

Yazılan Diziler'i kullanmak için bir ArrayBuffer ve bunun için bir görünüm oluşturmanız gerekir. En kolay yol, istenen boyutta ve türde bir yazılan dizi 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];

Birkaç farklı türde yazılmış dizi görünümü vardır. Hepsi aynı API'yi paylaşır. Yani bir API'yi kullanmayı öğrendikten sonra, hepsini nasıl kullanacağınızı neredeyse biliyorsunuzdur. Bir sonraki örnekte, mevcut olan her bir tür yazılı dizi görünümünden birini 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);

Sonuncusu biraz özeldir. Giriş değerlerini 0 ile 255 arasında sabitler. Artık 8 bit aralığının taşmasını önlemek için resim işleme hesaplamanızı manuel olarak sıkıştırmanız gerekmediğinden bu yöntem özellikle Canvas resim işleme algoritmaları için kullanışlıdır.

Örneğin, bir Uint8Array'de depolanan bir resme gama faktörünün nasıl uygulanacağını burada görebilirsiniz. Pek hoş değil:

u8[i] = Math.min(255, Math.max(0, u8[i] * gamma));

Uint8ClampedArray ile manuel sınırlamayı atlayabilirsiniz:

pixels[i] *= gamma;

Yazılan dizi görünümleri oluşturmanın diğer yolu, önce bir ArrayBuffer ve daha sonra bunu gösteren görünümler oluşturmaktır. Harici verileri alan API'ler genellikle ArrayBuffers ile çalışır. Dolayısıyla bu API'ler için yazılan dizi görünümünü 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 için birden fazla görünümünüz de olabilir.

var fa = new Float32Array(64);
var ba = new Uint8Array(fa.buffer, 0, Float32Array.BYTES_PER_ELEMENT); // First float of fa.

Yazılan bir diziyi başka bir yazılan diziye kopyalamanın en hızlı yolu, yazılan dizi kümesi yöntemini kullanmaktır. Memcpy benzeri bir kullanım için görünümlerin arabelleklerinde Uint8Arrays oluşturun ve verileri kopyalamak için set 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 arabelleğe almak için bir DataView kullanmaktır. Başlığınızda 8 bitlik imzasız int, ardından iki adet 16 bitlik int ve 32 bitlik kaymalardan oluşan bir yük dizisinden oluşan bir dosya biçimimiz olduğunu varsayalım. Bu yöntemi yazılan dizi görünümleriyle tekrar okumak da yapılabilir ancak biraz sıkıntı yaratır. 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 big-endian'dır. Tampondaki değerler small-endian ise isteğe bağlı smallEndian parametresini getter'a geçirebilirsiniz:

...
var width = dv.getUint16(1, true);
var height = dv.getUint16(3, true);
...
vectors[i] = dv.getFloat32(off, true);
...

Yazılan dizi görünümlerinin her zaman yerel bayt sırasına göre olduğunu unutmayın. Bunun amacı, onları hızlandırmaktır. Endiannın sorun yaratacağı verileri okuyup yazmak için DataView kullanmanız gerekir.

DataView, değerleri arabelleğe alma yöntemlerine de sahiptir. Bu belirleyiciler, 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

Endianness veya bayt sırası, çok baytlı sayıların bilgisayarın belleğinde depolanma sırasıdır. big-endian terimi, en önemli baytı ilk sırada depolayan CPU mimarisini, little-endian ise en az belirgin baytı ilk sırada depolar. Belirli bir CPU mimarisinde hangi bitiş gücünün kullanıldığı tamamen rastgeledir; bunlardan birini seçmek için iyi nedenler vardır. Aslında bazı CPU'lar hem büyük hem de küçük uçlu verileri destekleyecek şekilde yapılandırılabilir.

Neden endianness konusunda endişelenmeniz gerekiyor? Bunun nedeni basit. Diskten veya ağdan veri okurken ya da yazarken veri bitişi belirtilmelidir. Bu, üzerinde çalışan CPU'nun uç durumu ne olursa olsun verilerin doğru bir şekilde yorumlanmasını sağlar. Ağların giderek yaygınlaştığı dünyamızda, sunuculardan veya ağdaki diğer eşlerden gelen ikili program verileriyle çalışması gerekebilecek her tür cihazı (büyük veya küçük uçlu) doğru şekilde desteklemek büyük önem taşıyor.

DataView arayüzü, dosyalar ve ağdan veri okuyup yazmak için özel olarak tasarlanmıştır. DataView, belirli bir bitiş alanına sahip veriler üzerinde çalışır. Büyük veya küçük, her değer için her erişimde bitiş değeri belirtilmelidir. Böylece, tarayıcının çalıştığı CPU'nun uç durumu ne olursa olsun, ikili program verilerini okurken veya yazarken tutarlı ve doğru sonuçlar elde edersiniz.

Genellikle, uygulamanız bir sunucudan ikili veri okuduğunda, bunları uygulamanızın dahili olarak kullandığı veri yapılarına dönüştürmek için bir kez tarama yapmanız gerekir. Bu aşamada DataView kullanılmalıdır. Yazılan dizi görünümleri CPU'nun yerel bitiş değerini kullandığından, çok baytlı dizi görünümlerini (Int16Array, Uint16Array vb.) doğrudan XMLHttpRequest, FileReader veya başka bir giriş/çıkış API'si aracılığıyla getirilen verilerle kullanmak iyi bir fikir değildir. Bu konuyla ilgili daha fazla bilgi vereceğiz.

Birkaç basit örneğe göz atalım. Windows BMP dosya biçimi, Windows'un ilk günlerinde resimleri depolamak için kullanılan standart biçim olarak biliniyordu. Yukarıda bağlantısı verilen dokümanlar, dosyadaki tüm tam sayı değerlerinin small-endian biçiminde saklandığını açıkça belirtmektedir. Bu makaleye eşlik eden DataStream.js kitaplığını kullanarak BMP başlığının başlangıcını ayrıştıran bir kod snippet'ini burada görebilirsiniz:

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();
  // ...
}

Burada başka bir örneği görebilirsiniz. Bu örnek, WebGL örnekleri projesindeki Yüksek Dinamik Aralık oluşturma demosu arasındadır. Bu demo, yüksek dinamik aralıklı dokuları temsil eden ham, küçük uçlu 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, sunucudan XMLHttpRequest aracılığıyla 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 veri alındıktan sonra, DataView ile bunun üzerinden tek bir geçiş yapılmalıdır. Bağımsız sayısal değerleri okuyun ve bunları bir JavaScript nesnesi (az miktarda yapılandırılmış veri için) veya yazılan bir dizi görünümü (büyük veri blokları için) gibi başka bir veri yapısında depolayın. Bu, kodunuzun her tür CPU'da doğru şekilde çalışmasını sağlar. Ayrıca, bir dosyaya veya ağa veri yazmak için DataView kullanın ve oluşturduğunuz veya kullandığınız dosya biçimini üretmek için çeşitli set yöntemlerinde littleEndian bağımsız değişkenini uygun şekilde belirttiğinizden emin olun.

Ağ üzerinden geçen tüm verilerin dolaylı olarak bir biçimi ve bitişi olduğunu (en azından tüm çok baytlık değerler için) unutmayın. Uygulamanızın ağ üzerinden gönderdiği tüm verilerin biçimini net bir şekilde tanımladığınızdan ve belgelediğinizden emin olun.

Yazılan Diziler kullanan tarayıcı API'leri

Şu anda Yazılan Diziler'i kullanan farklı tarayıcı API'larına ilişkin kısa bir genel bakış sunacağız. Geçerli kesme; WebGL, Tuval, Web Audio API, XMLHttpRequests, WebSockets, Web Workers, Media Source API ve Dosya API'lerini içerir. API listesinden, Türetilmiş Diziler'in, verilerin verimli bir şekilde iletilmesinin yanı sıra performans açısından hassas multimedya çalışmaları için uygun olduğunu görebilirsiniz.

WebGL

Yazılan Diziler ilk kez, arabellek ve görüntü verileri arasında geçiş yapmak için kullanılan WebGL'dir. WebGL arabellek nesnesinin içeriğini ayarlamak için bir Yazılan Dizi 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);

Yazılı Diziler, doku verilerini geçirmek için de kullanılır. Burada, Doku içeriğini Yazılan Dizi kullanarak geçirmeye ilişkin temel bir örnek gösterilmektedir.

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'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);

2D Tuval

Yakın zamanda Canvas ImageData nesnesi, Yazılı Diziler spesifikasyonuyla çalışacak şekilde yapılmıştır. Artık bir tuval öğesindeki piksellerin Yazılı Diziler gösterimini alabilirsiniz. Artık tuval öğesiyle uğraşmak zorunda kalmadan tuval piksel dizileri oluşturup düzenleyebileceğiniz için bu yararlı olur.

var imageData = ctx.getImageData(0,0, 200, 100);
var typedArray = imageData.data // data is a Uint8ClampedArray

XMLHttpRequest2

XMLHttpRequest, bir Yazılan Dizi desteği elde etti ve artık bir JavaScript dizesini bir Yazılı Dizi halinde ayrıştırmak zorunda kalmadan bir Yazılı Dizi yanıtı alabilirsiniz. Bu, getirilen verileri doğrudan multimedya API'larına iletmek ve ağdan getirilen ikili dosyaları ayrıştırmak için çok kullanışlıdır.

Tek yapmanız gereken XMLHttpRequest nesnesinin responseType'ını "arraybuffer" olarak ayarlamaktır.

xhr.responseType = 'arraybuffer';

Ağdan veri indirirken bitiş avantajlarıyla ilgili sorunları göz önünde bulundurmanız gerektiğini unutmayın. Yukarıdaki endianness ile ilgili bölüme bakın.

Dosya API'leri

Dosya Okuyucu, dosya içeriğini ArrayBuffer olarak okuyabilir. Ardından, içeriğini değiştirmek için arabelleğe yazılan dizi görünümlerini ve DataView'ları ekleyebilirsiniz.

reader.readAsArrayBuffer(file);

Bu durumda da Endianni göz önünde bulundurmalısınız. Ayrıntılar için Endianness bölümüne göz atın.

Aktarılabilir nesneler

postMessage'daki aktarılabilir nesneler, ikili verilerin diğer pencerelere ve Web İşçilerine geçirilmesini çok daha hızlı hale getirir. Bir nesneyi Aktarılabilir olarak bir çalışana gönderdiğinizde, nesne, gönderen ileti dizisinde erişilemez hale gelir ve alıcı Çalışan nesnenin sahipliğini alır. Bu, gönderilen verilerin kopyalanmadığı, yalnızca Yazılan Dizinin sahipliğinin alıcıya aktarıldığı yüksek düzeyde optimize bir uygulama sağlar.

Aktarılabilir nesneleri Web İşçileri ile kullanmak için çalışanda webkitPostMessage yöntemini kullanmanız gerekir. webkitPostMessage yöntemi, postMessage ile aynı şekilde çalışır, ancak yalnızca bir yerine iki bağımsız değişken alır. Eklenen ikinci bağımsız değişken, çalışana aktarmak istediğiniz nesne dizisidir.

worker.webkitPostMessage(oneGBTypedArray, [oneGBTypedArray]);

Çalışan, nesneleri çalışandan geri almak için bunları aynı şekilde ana iş parçacığına geri aktarabilir.

webkitPostMessage({results: grand, youCanHaveThisBack: oneGBTypedArray}, [oneGBTypedArray]);

Sıfır kopya!

Medya Kaynağı API'sı

Son zamanlarda, medya öğeleri de Medya Kaynağı API'sı biçiminde bir tür Yazılmış Dizi avantajı elde etti. Video verileri içeren bir Typed Array'i doğrudan webkitSourceAttach kullanarak video öğesine geçirebilirsiniz. Bu işlem, video öğesinin mevcut videodan sonra video verilerini eklemesini sağlar. SourceAttach, geçiş reklamları, oynatma listeleri, akış ve tek bir video öğesi kullanarak birkaç video oynatmak istediğiniz diğer kullanımlar için mükemmeldir.

video.webkitSourceAppend(uint8Array);

İkili WebSockets

Ayrıca, tüm verilerinizi dizeye dönüştürmek zorunda kalmamak için WebSockets ile Yazılmış Diziler kullanabilirsiniz. Verimli protokoller yazmak ve ağ trafiğini en aza indirmek için idealdir.

socket.binaryType = 'arraybuffer';

Sevindim! Böylece API incelemesi tamamlandı. Typed Diziler ile ilgili üçüncü taraf kitaplıklara bakalım.

Üçüncü taraf kitaplıkları

jDataView

jDataView, tüm tarayıcılar için bir DataView kaplaması uygular. DataView önceden yalnızca WebKit ile kullanılabilen bir özellikti, ancak artık diğer tarayıcıların çoğu tarafından destekleniyor. Mozilla geliştirici ekibi, DataView'ı Firefox'ta da etkinleştirmek için bir yama indirme işlemlerini yürütmektedir.

Chrome Geliştirici İlişkileri ekibinden Eric Bidelman, jDataView kullanan küçük bir MP3 ID3 etiketi okuyucu örneği yazdı. Blog yayınındaki bir kullanım örneğini burada görebilirsiniz:

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.
}

dize kodlaması

Yazılmış Diziler'de dizelerle çalışmak şu anda biraz zor olsa da size yardımcı olacak stringencoding kitaplığı vardır. Dize kodlaması, önerilen Türülü Dizi dizesi kodlama spesifikasyonunu uygular. Böylece, dizenin koda girilmesi de nelerin beklendiği ile ilgili fikir edinmenin iyi bir yoludur.

Aşağıda, dize kodlamasının temel bir kullanım örneği verilmiştir:

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 bit düzenleme kitaplığı yazdım. Adından da anlaşılacağı gibi, DataView, bitlerle birlikte çalışması dışında, DataView'a çok benzer. BitView ile, ArrayBuffer içinde belirli bir bit ofsetindeki bir bitin değerini alabilir ve ayarlayabilirsiniz. BitView, rastgele bit ofsetlerinde 6 bit ve 12 bit girişleri depolama ve yükleme yöntemlerine de sahiptir.

Ekran koordinatlarıyla çalışmak için 12 bit int'ler uygundur, çünkü ekranların uzun kenarı boyunca 4096'dan daha az piksel içerir. 32 bit int yerine 12 bit int kullandığınızda boyuttan% 62 küçülmüş olursunuz. Daha uç bir örnek vermek gerekirse, koordinatlar için 64 bitlik kayan değerler kullanan şekil dosyalarıyla çalışıyordum, ancak model yalnızca ekran boyutunda gösterileceğinden kesinliğe ihtiyacım yoktu. Önceki koordinattaki değişiklikleri kodlamak için 6 bitlik delta içeren 12 bit taban koordinatlara geçmek, dosya boyutunu onda birliğe indirdi. Bununla ilgili demoyu burada görebilirsiniz.

Aşağıda, BitView.js'nin kullanımına ilişkin bir örnek verilmiştir:

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ılan dizilerle ilgili en heyecan verici şeylerden biri, JavaScript'te ikili dosyalarla çalışmayı kolaylaştırmalarıdır. Bir dize karakterini karakter bazında ayrıştırmak ve bunları manuel olarak ikili sayılara dönüştürmek yerine, artık XMLHttpRequest ile bir ArrayBuffer alabilir ve bunu bir DataView kullanarak doğrudan işleyebilirsiniz. Bu, örneğin bir MP3 dosyası yüklemeyi ve ses çalarınızda kullanmak ü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. Veya EXIF etiketlerini bir JPEG’den okuyun ve slayt gösterisi uygulamanızda gösterin.

ArrayBuffer XHR'lerle ilgili sorun, arabellekteki yapısal benzeri verileri okumanın biraz acı verici olmasıdır. DataView, endian açısından güvenli bir şekilde aynı anda birkaç sayıyı okumak için kullanışlıdır. Yazılan dizi görünümleri, öğe boyutuna ayarlı yerel endian sayı dizilerini okumak için yararlıdır. Eksik hissettiğimiz şey, verilerin dizelerini ve yapılarını kullanışlı bir şekilde endüstri açısından güvenli bir şekilde okumanın bir yolunu bulmaktı. DataStream.js girin.

DataStream.js, ArrayBuffers'dan dosya benzeri bir şekilde skaler değerler, dizeler, diziler ve struct'lar okuyan ve yazan bir Typed Arrays kitaplığıdır.

Bir ArrayBuffer öğesindeki kayan bir dizide 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'nin asıl yararlı olduğu yer, daha karmaşık verilerin okunmasıdır. JPEG işaretçilerinde 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 dizisini alır. Karmaşık türleri işlemek için geri çağırma işlevleri vardır ve veri dizileri ile iç içe yerleştirilmiş struct'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 struct tanımı, [ad, tür] çiftlerinden oluşan düz bir dizidir. İç içe yerleştirilmiş struct'lar, tür için bir dizi kullanılarak yapılır. Diziler, üç öğeli bir dizi kullanılarak tanımlanır. Bu ikinci öğe dizi öğe türü, üçüncü öğe ise dizi uzunluğudur (sayı olarak, önceden okunan alana başvuru olarak veya geri çağırma işlevi olarak). Dizi tanımının ilk öğesi kullanılmıyor.

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 verilerinde okumanın canlı bir örneğini burada görebilirsiniz. Demoda, JPEG dosyasının etiket düzeyindeki yapısını (bazı EXIF ayrıştırmalarıyla birlikte) okumak için DataStream.js, JavaScript'te JPEG resminin kodunu çözmek ve görüntülemek için jpg.js kullanılmaktadır.

Yazılan Dizilerin Geçmişi

Yazılan Diziler, JavaScript dizilerini grafik sürücüsüne geçirmenin performans sorunlarına neden olduğunu tespit ettiğimizde WebGL'nin ilk uygulama aşamasında başladı. JavaScript dizilerinde, WebGL bağlamanın yerel bir dizi tahsis etmesi ve JavaScript dizisinin üzerinde yürüyerek bunu doldurması ve dizideki her JavaScript nesnesini gerekli yerel türe yayınlaması gerekiyordu.

Mozilla'dan Vladimir Vukicevic, veri dönüştürme sorununu gidermek için CanvasFloatArray: JavaScript arayüzüne sahip C stili bir kayan diziyi yazdı. Artık CanvasFloatArray'ı JavaScript'te düzenleyebilir ve bağlamada fazladan herhangi bir işlem yapmanıza gerek kalmadan doğrudan WebGL'ye geçirebilirsiniz. Daha sonraki yinelemelerde CanvasFloatArray olarak WebGLFloatArray olarak yeniden adlandırıldı. Bu ad, Float32Array olarak yeniden adlandırıldı ve arabelleğe erişmek için yedek ArrayBuffer ve yazılan Float32Array görünümüne bölündü. Diğer tam sayı ve kayan nokta boyutları ile işaretli/imzasız varyantlar için de türler eklendi.

Tasarımla ilgili dikkat edilmesi gerekenler

Başından itibaren, Yazılı Diziler'in tasarımında ikili verilerin yerel kitaplıklara etkili bir şekilde aktarılmasına duyulan ihtiyaç vardı. Bu nedenle, yazılan dizi görünümleri, ana makine CPU'sunun yerel uç düzeyinde uyumlu hale getirilmiş veriler üzerinde çalışır. Bu kararlar, köşe verilerini grafik kartına göndermek gibi işlemler sırasında JavaScript'in maksimum performansa ulaşmasını mümkün kılar.

DataView, verilerin her zaman belirli bir bitiş oranına sahip olduğu ve maksimum performans için hizalanmayabileceği dosya ve ağ G/Ç'si için özel olarak tasarlanmıştır.

Bellek içi veri derlemesi (yazılmış dizi görünümleri kullanılarak) ve G/Ç (DataView kullanarak) arasındaki tasarım ayrımı bilinçli bir süreçti. Modern JavaScript motorları, yazılan dizi görünümlerini büyük ölçüde optimize eder ve bunlarla sayısal işlemlerde yüksek performans sağlar. Yazılan dizi görünümlerinin mevcut performans düzeyleri, bu tasarım kararıyla mümkün olmuştur.

Referanslar