Tablice z określonymi danymi – dane binarne w przeglądarce.

Ilmari Heikkinen

Wstęp

Tablice typu typed to stosunkowo nowy dodatek do przeglądarek, który powstał ze względu na potrzebę wydajnej obsługi danych binarnych w WebGL. Tablica z typami to płyta pamięci z wpisanym widokiem, podobnie jak w języku C. Tablica z typami jest oparta na nieprzetworzonej pamięci, więc mechanizm JavaScript może przekazywać pamięć bezpośrednio do bibliotek natywnych bez konieczności żmudnej konwertowania danych na ich reprezentację natywną. W efekcie tablice z typem wpisywane do przekazywania danych do WebGL i innych interfejsów API do obsługi danych binarnych są znacznie skuteczniejsze niż tablice JavaScript.

Widoki tablicy z typami działają jak tablice jednego typu dla segmentu obiektu ArrayBuffer. Istnieją widoki dla wszystkich typowych typów liczb z samoopisowymi nazwami, takimi jak Float32Track, Float64Array, Int32Array czy Uint8Array. Dostępny jest też specjalny widok, który zastąpił typ tablicy pikseli w funkcji ImageData: Uint8ClampedArray w Canvas.

DataView to drugi typ widoku, który służy do obsługi danych heterogenicznych. Zamiast interfejsu API przypominającego tablicę obiekt DataView udostępnia interfejs API get/set do odczytu i zapisywania dowolnych typów danych z dowolnymi przesunięciami bajtów. DataView doskonale nadaje się do odczytywania i zapisywania nagłówków plików oraz innych danych podobnych do struct.

Podstawy korzystania z tablic z typami

Widoki tablicy z typem

Aby korzystać z tablic typu, musisz utworzyć obiekt ArrayBuffer i jego widok. Najprostszym sposobem jest utworzenie widoku tablicy z danymi o żądanym rozmiarze i typie.

// 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];

Jest kilka różnych typów widoków tablicy z typami. Korzystają one z tego samego interfejsu API, więc gdy nauczysz się, jak używać jednego z nich, będziesz wiedzieć, jak używać każdego z nich. W następnym przykładzie utworzę po jednym z każdego istniejącego widoku tablicy o typie.

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

Ostatnia opcja jest nieco specjalna i ogranicza wartości wejściowe od 0 do 255. Jest to szczególnie przydatne w przypadku algorytmów przetwarzania obrazów Canvas, ponieważ nie trzeba już ręcznie ograniczać zakresu przetwarzania obrazu, aby uniknąć przekroczenia 8-bitowego zakresu.

W ten sposób można na przykład zastosować współczynnik gamma do obrazu zapisanego w tablicy Uint8Array. Niezbyt ładne:

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

Funkcja Uint8ClampedArray pozwala pominąć ręczne ograniczanie zakresu:

pixels[i] *= gamma;

Innym sposobem na tworzenie widoków tablicy z typami jest utworzenie najpierw obiektu ArrayBuffer, a potem widoków, które do niego prowadzą. Interfejsy API, które pobierają dane zewnętrzne, zwykle działają w klastrze ArrayBuffers, więc w ten sposób można uzyskać widok tablicy z typem.

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

Do tego samego obiektu ArrayBuffer możesz mieć również kilka widoków danych.

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

Aby skopiować typ tablicy do innej tablicy o typie, najszybszym sposobem jest użycie metody ustawiania tablicy o określonym typie. Aby skorzystać z metody przypominającej memcpy, utwórz obiekty Uint8Track w buforach widoków i użyj parametru „set”, aby skopiować dane.

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

Aby użyć obiektów ArrayBuffer, które zawierają dane heterogeniczne, najprostszym sposobem jest użycie obiektu DataView do bufora. Załóżmy, że mamy format pliku z nagłówkiem z 8-bitowym int bez znaku, po którym następuje 2 16-bitowe int, po którym następuje tablica ładunku z 32-bitową liczbą zmiennoprzecinkową. Odczytanie informacji w widokach tablicy z typem jest proste, ale bywa kłopotliwe. Dzięki DataView możemy odczytać nagłówek i użyć widoku tablicy z typem w przypadku tablicy zmiennoprzecinkowej.

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

W powyższym przykładzie wszystkie odczytane wartości to big-endian. Jeśli wartości w buforze mają charakter little-endian, możesz przekazać do metody getter opcjonalny parametr littleEndian:

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

Pamiętaj, że widoki tablicy o typach zawsze występują w natywnej kolejności bajtów. Chodzi o to, aby były szybkie. Do odczytywania i zapisywania danych w miejscach, w których istnieje problem, należy używać obiektu DataView.

Obiekt DataView ma też metody zapisywania wartości w buforach. Nazwy tych parametrów są takie same jak w metodach pobierania: „set”, po którym następuje typ danych.

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

Rozmowa o endiannessie

Endianność, czyli porządek bajtów, to kolejność, w jakiej liczby wielobajtowe są przechowywane w pamięci komputera. Termin big-endian opisuje architekturę procesora, w której najpierw jest przechowywany największy bajt; little-endian, czyli najpierw najmniej istotny bajt. To, która endianność jest używana w danej architekturze procesora, jest całkowicie dowolna i istnieje uzasadnione powody, aby wybrać którąś z tych metod. Niektóre procesory można skonfigurować tak, aby obsługiwały zarówno dane big-endian, jak i little-endian.

Dlaczego trzeba się martwić o endianność? Powód jest prosty. Podczas odczytywania lub zapisywania danych z dysku lub sieci należy określić ich aktualność. Zapewnia to prawidłowe interpretowanie danych niezależnie od mocy procesora, z którego korzystają. W świecie, w którym jest coraz więcej sieci sieciowych, kluczowa jest odpowiednia obsługa wszystkich rodzajów urządzeń, zarówno małych, jak i dużych, które mogą wymagać obsługi danych binarnych pochodzących z serwerów lub innych sieci równorzędnych w sieci.

Interfejs DataView został zaprojektowany specjalnie do odczytu i zapisu danych w plikach oraz w sieci i z nich. DataView działa na danych o określonej terminologii. Endianność, zarówno duża, jak i mała, musi być określana przy każdym dostępie do każdej wartości, co zapewnia uzyskanie spójnych i poprawnych wyników podczas odczytywania lub zapisywania danych binarnych, niezależnie od intensywności procesora, na którym działa przeglądarka.

Zwykle gdy aplikacja odczytuje dane binarne z serwera, trzeba je raz przeskanować, aby przekonwertować je na struktury danych używane wewnętrznie przez aplikację. Na tym etapie należy używać DataView. Nie warto używać widoków tablicy wielobajtowej (Int16Array, Uint16Array itp.) bezpośrednio z danymi pobranymi przez XMLHttpRequest, FileReader lub dowolny inny interfejs API do wprowadzania/wyjściowy danych wejściowych, ponieważ w widokach tablicy typów używana jest rodzima endianność procesora. Więcej na ten temat znajdziesz później.

Przyjrzyjmy się kilku prostym przykładom. Na początku funkcjonowania systemu Windows format pliku Windows BMP był standardowym formatem przechowywania obrazów. W dokumentacji, do której link znajduje się powyżej, jasno wynika, że wszystkie wartości całkowite w pliku są przechowywane w formacie little-endian. Oto fragment kodu, który analizuje początek nagłówka BMP przy użyciu dołączonej do tego artykułu biblioteki DataStream.js:

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

Oto kolejny przykład pochodzący z prezentacji renderowania High Dynamic Range w przykładowym projekcie WebView. Ta wersja demonstracyjna pobiera nieprzetworzone dane zmiennoprzecinkowe małych punktów końcowych reprezentujące tekstury o wysokim zakresie dynamicznym i trzeba je przesłać do WebGL. Oto fragment kodu, który prawidłowo interpretuje wartości zmiennoprzecinkowe we wszystkich architekturach procesora. Przyjmijmy, że zmienna „arrayBuffer” to obiekt ArrayBuffer, który właśnie został pobrany z serwera za pomocą XMLHttpRequest:

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

Ogólną zasadą jest: po otrzymaniu danych binarnych z serwera WWW wykonaj jedną z tych czynności, korzystając z DataView. Odczyt poszczególnych wartości liczbowych i zapisz je w jakiejś innej strukturze danych: obiekt JavaScript (w przypadku niewielkich ilości uporządkowanych danych) lub widok tablicy z danymi typowymi (w przypadku dużych bloków danych). Dzięki temu kod będzie działał prawidłowo na wszystkich rodzajach procesorów. Użyj też DataView do zapisania danych w pliku lub w sieci. Pamiętaj, by odpowiednio określić argument littleEndian dla różnych metod set, aby uzyskać format pliku, który tworzysz lub używasz.

Pamiętaj, że wszystkie dane przesyłane przez sieć domyślnie mają format i końcówkę (co najmniej w przypadku wartości wielobajtowych). Pamiętaj, aby dokładnie określić i udokumentować format wszystkich danych, które aplikacja wysyła w sieci.

Interfejsy API przeglądarek korzystające z tablic z typami

Omówię pokrótce różne interfejsy API przeglądarek, które obecnie używają tablic z typami. Obecnie przycięcie obejmuje WebGL, Canvas, Web Audio API, XMLHttpRequests, WebSockets, Web Workers, Media Source API oraz File API. Z listy interfejsów API wynika, że tablice z typami są przystosowane do obsługi multimediów, w których liczy się wydajność, oraz wydajnego przekazywania danych.

WebGL

Tablice typu typed po raz pierwszy zostały zastosowane w WebGL, gdzie używa się do przekazywania danych bufora i danych obrazów. Aby ustawić zawartość obiektu bufora WebGL, użyj wywołania gl.bufferData() z tablicą Typed.

var floatArray = new Float32Array([1,2,3,4,5,6,7,8]);
gl.bufferData(gl.ARRAY_BUFFER, floatArray);

Tablice typu umożliwiają również przekazywanie danych tekstur. Oto podstawowy przykład przekazywania treści tekstury przy użyciu tablicy typów.

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

Tablice typu typed są potrzebne także do odczytywania pikseli z kontekstu WebGL.

var pixels = new Uint8Array(320*240*4); // 320x240 RGBA image
gl.readPixels(0, 0, 320, 240, gl.RGBA, gl.UNSIGNED_BYTE, pixels);

Canvas 2D

Niedawno przygotowaliśmy obiekt Canvas ImageData do korzystania ze specyfikacji tablic typu. Teraz możesz zobaczyć piksele w elemencie canvas za pomocą tablic typu. Jest to przydatne, ponieważ teraz można również tworzyć i edytować tablice pikseli canvas, bez potrzeby używania elementów canvas.

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

XMLHttpRequest2

W przypadku żądania XMLHttpRequest została zastosowana ulepszona tablica typu. Teraz możesz otrzymywać odpowiedzi typu tablicowe bez konieczności analizowania ciągu JavaScript w tablicę typu. Jest to niezwykle przydatne w przypadku przekazywania pobranych danych bezpośrednio do interfejsów API multimediów oraz analizowania plików binarnych pobranych z sieci.

Wystarczy ustawić parametr „responseType” obiektu XMLHttpRequest na „arraybuffer”.

xhr.responseType = 'arraybuffer';

Pamiętaj, że przy pobieraniu danych z sieci musisz mieć świadomość problemów związanych z Endiannessem! Zapoznaj się z powyższą sekcją dotyczącą Endianness.

Interfejsy API plików

Moduł FileReader może odczytywać zawartość pliku jako obiekt ArrayBuffer. Następnie możesz dołączyć do bufora widoki tablicy o typach i DataViews, aby manipulować jego zawartością.

reader.readAsArrayBuffer(file);

Pamiętaj też o końcowości. Więcej informacji znajdziesz w sekcji dotyczącej Endianness.

Obiekty możliwe do przeniesienia

Przenoszone obiekty w metodzie postMessage znacznie przyspieszają przekazywanie danych binarnych do innych okien, a zasoby Web Workers. Gdy wyślesz obiekt do instancji roboczej z możliwością przeniesienia, obiekt staje się niedostępny w wątku wysyłającym, a podmiot roboczy staje się jego właścicielem. Umożliwia to wysoce zoptymalizowaną implementację, w której wysyłane dane nie są kopiowane, a własność tablicy typów jest przekazywana do odbiorcy.

Aby używać obiektów Transferable w instancjach roboczych Web Workers, musisz użyć w tej instancji metody webkitPostMessage. Metoda webkitPostMessage działa tak samo jak postMessage, ale wymaga dwóch argumentów zamiast jednego. Dodany drugi argument to tablica obiektów, które chcesz przekazać do instancji roboczej.

worker.webkitPostMessage(oneGBTypedArray, [oneGBTypedArray]);

Aby odzyskać obiekty z instancji roboczej, może ona w ten sam sposób przekazać je z powrotem do głównego wątku.

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

Brak kopii, hurra!

Interfejs API Media Source

Ostatnio elementy multimedialne zyskały też nową funkcję tablicy Typed pod postacią interfejsu Media Source API. Tablicę typu zawierającą dane wideo możesz przekazać bezpośrednio do elementu wideo za pomocą metody webkitSourcejoin. W ten sposób element video będzie dołączał dane wideo po już istniejącym filmie. Świetnie sprawdza się przy tworzeniu reklam pełnoekranowych, playlist, strumieniowania i innych zastosowań, w których można odtworzyć kilka filmów za pomocą jednego elementu wideo.

video.webkitSourceAppend(uint8Array);

Binarne protokół WebSocket

Możesz też używać tablic typu w połączeniu z WebSockets, aby uniknąć konieczności łączenia wszystkich danych w ciąg znaków. Świetnie sprawdza się przy pisaniu efektywnych protokołów i minimalizowaniu ruchu w sieci.

socket.binaryType = 'arraybuffer';

Uff! To koniec weryfikacji interfejsu API. Przejdźmy do zewnętrznych bibliotek do obsługi tablic z typem.

Biblioteki innych firm

jDataView

Metoda jDataView implementuje podkładkę DataView w przypadku wszystkich przeglądarek. Kiedyś funkcja DataView była dostępna wyłącznie w WebKit, ale obecnie jest obsługiwana przez większość innych przeglądarek. Zespół programistów Mozilla jest w trakcie wprowadzania poprawki umożliwiającej włączenie DataView także w przeglądarce Firefox.

Eric Bidelman z zespołu Chrome Developer Relations napisał przykład małego czytnika tagów MP3 ID3 wykorzystujący jDataView. Oto przykład użycia z posta na blogu:

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

kodowanie ciągu znaków

Praca z ciągami znaków w tablicy wpisanej z wyprzedzeniem jest obecnie dość uciążliwa, ale warto skorzystać z biblioteki kodowania ciągu znaków. Stringencoding stosuje proponowaną specyfikację kodowania ciągu znaków w tablicy Typed, więc jest to również dobry sposób na poznanie przyszłego kodu.

Oto podstawowy przykład użycia ciągu znaków:

var uint8array = new TextEncoder(encoding).encode(string);
var string = new TextDecoder(encoding).decode(uint8array);

BitView.js

Napisałam małą bibliotekę manipulacyjną dla tablic z typem o nazwie BitView.js. Jak sama nazwa wskazuje, działa on podobnie do DataView, z tym że działa z bitami. Za pomocą BitView można pobrać i ustawić wartość bitu w przypadku danego przesunięcia bitowego w obiekcie ArrayBuffer. BitView udostępnia też metody przechowywania i wczytywania 6-bitowych i 12-bitowych liczb całkowitych z dowolnymi przesunięciami bitowymi.

12-bitowe inty dobrze nadają się do pracy ze współrzędnymi ekranu, ponieważ dłuższe ekrany mają zwykle mniej niż 4096 pikseli. Użycie 12-bitowych liczb całkowitych zamiast 32-bitowych zmniejsza rozmiar o 62%. Bardziej skrajny przykład pracowałem z plikami kształtu, które używają 64-bitowych liczb zmiennoprzecinkowych do określania współrzędnych, ale precyzja była niepotrzebna, ponieważ model był wyświetlany tylko na podstawie rozmiaru ekranu. Przejście na 12-bitowe współrzędne podstawy z 6-bitowymi deltami w celu zakodowania zmian z poprzedniej współrzędnej spowodowało zmniejszenie rozmiaru pliku do jednej dziesiątej. Jego prezentację znajdziesz tutaj.

Oto przykład użycia biblioteki BitView.js:

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

Jedną z największych zalet tablic z typem jest to, że ułatwiają one obsługę plików binarnych w kodzie JavaScript. Zamiast analizować ciąg znaków po znaku i ręcznie konwertować te znaki na liczby binarne, możesz teraz uzyskać obiekt ArrayBuffer z użyciem XMLHttpRequest i przetworzyć go bezpośrednio za pomocą DataView. Ułatwia to np. wczytanie pliku MP3 i odczytanie tagów metadanych do wykorzystania w odtwarzaczu audio. Można też załadować plik kształtu i przekształcić go w model WebGL. Możesz też odczytać tagi EXIF z pliku JPEG i wyświetlić je w aplikacji do pokazu slajdów.

Problem z formatami ArrayBuffer XHRs polega na tym, że odczyt danych typu „struct” z bufora jest kłopotliwy. DataView jest przydatna do czytania kilku liczb jednocześnie w sposób bezpieczny dla Endian. Widoki tablic z danymi typowymi dobrze nadają się do odczytu tablic z ukierunkowanymi na rozmiar elementu macierzystych liczb Endian. Naszym zdaniem zabrakło nam sposobu na odczyt w tablicach i strukturach danych w wygodny sposób, który da się bezpiecznie skończyć. Wpisz DataStream.js.

DataStream.js to biblioteka typów tablic z typem, która odczytuje i zapisuje skalary, ciągi, tablice i struktury danych z ArrayBuffers w sposób podobny do plików.

Przykład odczytu w tablicy liczb zmiennoprzecinkowych z obiektu ArrayBuffer:

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

Technologia DataStream.js przydaje się przy odczytywaniu bardziej złożonych danych. Załóżmy, że korzystasz z metody, która odczytuje znaczniki JPEG:

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

Do odczytu struktur danych możesz też użyć metody DataStream.readStruct. Metoda readStruct przyjmuje tablicę definicji struct, która zawiera typy elementów tego elementu. Zawiera ona funkcje wywołań zwrotnych do obsługi typów złożonych, a także tablice danych i zagnieżdżone obiekty typu struct:

// 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
]);

Jak widać, definicja struktury struct to płaska tablica par [nazwa, typ]. Zagnieżdżone obiekty typu struct są wykonywane przez tablicę typu. Tablice są definiowane za pomocą tablicy 3-elementowej, w której drugi element to typ elementu tablicy, a trzeci element to długość tablicy (jako liczba, jako odwołanie do wcześniej odczytanego pola lub jako funkcja wywołania zwrotnego). Pierwszy element definicji tablicy nie jest używany.

Możliwe wartości:

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.

Tutaj znajdziesz przykład odczytu na żywo w metadanych JPEG. Wersja demonstracyjna wykorzystuje DataStream.js do odczytu struktury pliku JPEG na poziomie tagu (wraz z niektórymi elementami analizy EXIF) oraz jpg.js do dekodowania i wyświetlania obrazu JPEG w języku JavaScript.

Historia tablic typu

Tablice typu typed zaczęły działać na wczesnym etapie wdrożenia WebGL. Okazało się, że przekazywanie tablic JavaScript do sterownika grafiki powoduje problemy z wydajnością. W przypadku tablic JavaScript powiązanie WebGL musiało przydzielić tablicę natywną i wypełnić ją, przechodząc przez tablicę JavaScript, a następnie rzutując każdy obiekt JavaScript w tablicy na wymagany typ natywny.

Aby rozwiązać problem z wąskim gardłem konwersji danych, Vladimir Vukicevic stworzył CanvasFloatArray – tablicę zmiennoprzecinkową w stylu C z interfejsem JavaScript. Teraz można edytować obiekt CanvasFloatArray w języku JavaScript i przekazywać go bezpośrednio do WebGL bez konieczności wykonywania dodatkowych czynności związanych z wiązaniem. W kolejnych iteracjach nazwa CanvasFloatArray została zmieniona na WebGLFloatArray, a następnie została zmieniona na Float32Array i została podzielona na bazową tablica ArrayBuffer oraz typ Float32Array-view w celu uzyskania dostępu do bufora. Dodaliśmy też typy dla innych rozmiarów liczb całkowitych i zmiennoprzecinkowych oraz wersji podpisanych i niepodpisanych.

Uwagi dotyczące projektu

Tablice z typami od samego początku były konstruowane z myślą o konieczności sprawnego przekazywania danych binarnych do bibliotek natywnych. Z tego powodu widoki tablicy o typie działają na wyrównanych danych w natywnej terminologii hosta hosta. Dzięki tym decyzjam JavaScript może osiągnąć maksymalną wydajność podczas operacji, takich jak wysyłanie danych wierzchołków do karty graficznej.

DataView została zaprojektowana na potrzeby wejścia-wyjścia plików i sieci, gdzie dane zawsze mają określoną terminowość i mogą nie być dopasowane pod kątem maksymalnej wydajności.

Podział danych w pamięci (za pomocą widoków tablicy o typie) i wejście-wyjście (z użyciem DataView) był przemyślany. Nowoczesne mechanizmy JavaScriptu intensywnie optymalizują widoki tablic z typami i osiągają wysoką wydajność w przypadku operacji liczbowych. Ta decyzja projektowa pozwoliła osiągnąć obecne poziomy wydajności widoków tablicy z typami.

Źródła