Mekanika Kuantum Perhitungan dalam JS

Halo, nama saya Dmitry Karlovsky dan saya ... menganggur. Karena itu, saya punya banyak waktu luang untuk bermain musik, olahraga, kreativitas, bahasa, konferensi JS dan ilmu komputer. Saya akan memberi tahu Anda tentang penelitian terbaru di bidang $mol_fiber semi-otomatis perhitungan panjang menjadi kuanta kecil beberapa milidetik, yang menghasilkan perpustakaan miniatur $mol_fiber . Tapi pertama-tama, mari kita uraikan masalah yang akan kita pecahkan ..


Quanta!


Ini adalah versi teks dari kinerja eponymous di HolyJS 2018 Piter . Anda dapat membacanya sebagai artikel , atau membukanya di antarmuka presentasi , atau menonton video .


Masalah: Responsif yang rendah


Jika kita ingin memiliki 60 frame per detik yang stabil, maka kita hanya memiliki 16 frame dengan sepersekian milidetik untuk melakukan semua pekerjaan, termasuk apa yang dilakukan browser untuk menunjukkan hasil di layar.


Tetapi bagaimana jika kita mengambil alur untuk waktu yang lebih lama? Kemudian pengguna akan mengamati antarmuka yang tertinggal, menghambat animasi dan sejenisnya dari degradasi UX.


Daya tanggap yang rendah


Masalah: Tidak ada jalan keluar


Kebetulan saat kita melakukan perhitungan, hasilnya tidak lagi menarik bagi kita. Misalnya, kami memiliki gulir virtual, pengguna secara aktif menariknya, tetapi kami tidak dapat mengikutinya dan tidak dapat merender area yang sebenarnya hingga rendering mengembalikan kontrol untuk menangani peristiwa pengguna.


Tidak bisa diurungkan


Idealnya, tidak peduli berapa lama pekerjaan yang kita lakukan, kita harus terus memproses acara dan dapat setiap saat membatalkan pekerjaan yang telah kita mulai, tetapi belum selesai.


Saya cepat dan saya tahu itu


Tetapi bagaimana jika pekerjaan kita bukan satu, tetapi beberapa, tetapi satu aliran? Bayangkan Anda mengendarai teratai kuning yang baru dibeli dan pergi ke persimpangan kereta api. Saat gratis, Anda dapat menyelipkannya dalam hitungan detik. Tapi ..


Mobil keren


Masalah: Tidak ada konkurensi


Ketika persimpangan ditempati oleh kereta satu kilometer, Anda harus berdiri dan menunggu sepuluh menit sampai berlalu. Bukan untuk itu Anda membeli mobil sport, bukan?


Cepat, tunggu lambat


Dan betapa asyiknya jika kereta ini dibagi menjadi 10 kereta masing-masing 100 meter dan akan ada beberapa menit di antara mereka untuk melewatinya! Anda tidak akan terlambat kalau begitu.


Jadi apa solusi untuk masalah ini di dunia JS sekarang?


Solusi: Pekerja


Hal pertama yang terlintas dalam pikiran: mari kita letakkan semua perhitungan rumit ke dalam utas terpisah? Untuk melakukan ini, kami memiliki mekanisme untuk WebWorkers.


Logika pekerja


Acara dari aliran UI diteruskan ke pekerja. Di sana mereka diproses dan instruksi tentang apa dan bagaimana mengubah pada halaman sudah dikembalikan. Jadi, kami menyimpan aliran UI dari lapisan besar komputasi, tetapi tidak semua masalah diselesaikan dengan cara ini, dan di samping itu, masalah baru ditambahkan.


Pekerja: Masalah: Serialisasi (De)


Komunikasi antara aliran terjadi dengan mengirim pesan yang diserialisasi ke aliran byte, ditransfer ke aliran lain, dan di sana mereka diurai menjadi objek. Semua ini jauh lebih lambat daripada pemanggilan metode langsung dalam satu utas.


(De) serialisasi


Pekerja: Masalah: Asinkron saja


Pesan-pesan dikirimkan secara ketat secara tidak sinkron. Dan ini berarti bahwa beberapa fitur yang saya minta tidak tersedia. Misalnya, Anda tidak dapat menghentikan pendakian peristiwa ui dari pekerja, karena pada saat pawang dimulai, acara di utas UI sudah akan menyelesaikan siklus hidupnya.


Pesan antrian


Pekerja: Masalah: API Terbatas


API berikut ini tidak tersedia bagi kami di pekerja ..


  • DOM, CSSOM
  • Kanvas
  • Geolokasi
  • Sejarah & Lokasi
  • Sinkronkan permintaan http
  • XMLHttpRequest.responseXML
  • Jendela

Pekerja: Masalah: Tidak bisa membatalkan


Dan lagi, kita tidak punya cara untuk menghentikan perhitungan di woker.


Hentikan itu!


Ya, kita bisa menghentikan seluruh pekerja, tetapi itu akan menghentikan semua tugas di dalamnya.
Ya, Anda dapat menjalankan setiap tugas dalam pekerja yang terpisah, tetapi sangat intensif sumber daya.


Solusi: Bereaksi Fiber


Tentunya banyak yang mendengar FaceBook secara heroik menulis ulang Bereaksi, memecah semua perhitungan di dalamnya menjadi banyak fungsi kecil yang diluncurkan oleh penjadwal khusus.


Tricky React Fiber Logic


Saya tidak akan membahas detail implementasinya, karena ini adalah topik besar yang terpisah. Saya hanya akan mencatat beberapa fitur, karena itu mungkin tidak cocok untuk Anda ..


Bereaksi Fiber: Bereaksi diperlukan


Jelas, jika Anda menggunakan Angular, Vue, atau kerangka kerja lain selain Bereaksi, maka React Fiber tidak berguna untuk Anda.


Bereaksi Di Mana Saja!


React Fibre: Hanya rendering


Bereaksi - hanya mencakup lapisan rendering. Semua lapisan aplikasi lainnya dibiarkan tanpa kuantisasi.


Tidak aktif itu!


React Fibre tidak akan menyelamatkan Anda saat Anda perlu, misalnya, untuk memfilter sejumlah besar data dengan kondisi rumit.


React Fiber: Kuantisasi dinonaktifkan


Meskipun dukungan yang diklaim untuk kuantisasi, masih dinonaktifkan secara default, karena merusak kompatibilitas ke belakang.


Perangkap pemasaran


Kuantisasi dalam Bereaksi masih merupakan hal yang eksperimental. Berhati-hatilah!


Serat Bereaksi: Debug adalah rasa sakit


Ketika Anda mengaktifkan kuantisasi, callstack tidak lagi cocok dengan kode Anda, yang sangat menyulitkan proses debug. Tetapi kami akan kembali ke masalah ini.


Semua sakitnya debugging


Solusi: kuantisasi


Mari kita coba menggeneralisasi pendekatan React Fibre untuk menyingkirkan kerugian yang disebutkan. Kami ingin tetap berada dalam kerangka satu aliran, tetapi memecah perhitungan panjang menjadi kuanta kecil, di antaranya browser dapat membuat perubahan yang telah dibuat ke laman, dan kami akan merespons acara.


bagan nyala api


Di atas Anda melihat perhitungan panjang yang menghentikan seluruh dunia lebih dari 100 ms. Dan dari bawah - perhitungan yang sama, tetapi dipecah menjadi irisan waktu sekitar 16ms, yang memberi rata-rata 60 frame per detik. Karena kami biasanya tidak tahu berapa lama waktu yang diperlukan perhitungan, kami tidak dapat secara manual memecahnya menjadi potongan-potongan 16ms di muka. Oleh karena itu, kita memerlukan semacam mekanisme runtime yang mengukur waktu yang diperlukan untuk menyelesaikan tugas dan ketika kuantum terlampaui, yang menghentikan sementara eksekusi hingga frame animasi berikutnya. Mari kita pikirkan mekanisme apa yang kita miliki untuk mengimplementasikan tugas yang ditangguhkan di sini ..


Konkurensi: serat - stackfull coroutine


Dalam bahasa seperti Go dan D ada ungkapan seperti "coroutine with a stack", itu juga "fiber" atau "fiber".


 import { Future } from 'node-fibers' const one = ()=> Future.wait( future => setTimeout( future.return ) ) const two = ()=> one() + 1 const three = ()=> two() + 1 const four = ()=> three() + 1 Future.task( four ).detach() 

Dalam contoh kode, Anda melihat one fungsi, yang dapat menjeda serat saat ini, tetapi itu sendiri memiliki antarmuka yang sepenuhnya sinkron. two , three dan four fungsi adalah fungsi sinkron biasa yang tidak tahu apa-apa tentang serat. Di dalamnya Anda dapat menggunakan semua fitur javascript secara penuh. Dan akhirnya, pada baris terakhir, kita cukup menjalankan four fungsi dalam serat yang terpisah.


Menggunakan serat cukup mudah, tetapi untuk mendukungnya, Anda memerlukan dukungan runtime, yang tidak dimiliki sebagian besar penerjemah JS. Namun, untuk NodeJS ada ekstensi node-fibers yang menambahkan dukungan ini. Sayangnya, tidak ada browser yang tersedia di browser apa pun.


Concurrency: FSM - coroutine tanpa tumpukan


Dalam bahasa seperti C # dan sekarang JS ada dukungan untuk "stackless coroutines" atau "asynchronous functions." Fungsi-fungsi tersebut adalah mesin negara di bawah kap dan tidak tahu apa-apa tentang tumpukan, jadi Anda harus menandainya dengan kata kunci khusus "async", dan tempat-tempat di mana mereka dapat berhenti - "menunggu".


 const one = ()=> new Promise( done => setTimeout( done ) ) const two = async ()=> ( await one() ) + 1 const three = async ()=> ( await two() ) + 1 const four = async ()=> ( await three() ) + 1 four() 

Karena kita mungkin perlu menunda perhitungan kapan saja, ternyata hampir semua fungsi dalam aplikasi harus dibuat tidak sinkron. Ini tidak hanya kompleksitas kode, tetapi juga sangat mempengaruhi kinerja. Selain itu, banyak API yang menerima panggilan balik masih tidak mendukung panggilan balik yang tidak sinkron. Contoh mencolok adalah metode reduce array apa pun.


Concurrency: semi-fiber - restart


Mari kita coba melakukan sesuatu yang mirip dengan fiber, hanya menggunakan fitur-fitur yang tersedia untuk kita di browser modern apa pun ..


 import { $mol_fiber_async , $mol_fiber_start } from 'mol_fiber/web' const one = ()=> $mol_fiber_async( back => setTimeout( back ) ) const two = ()=> one() + 1 const three = ()=> two() + 1 const four = ()=> three() + 1 $mol_fiber_start( four ) 

Seperti yang Anda lihat, fungsi perantara tidak tahu apa-apa tentang gangguan - ini adalah JS biasa. Hanya one fungsi yang tahu tentang kemungkinan suspensi. Untuk membatalkan perhitungan, dia hanya melempar Promise sebagai pengecualian. Pada baris terakhir, kita menjalankan four fungsi dalam pseudo-fiber yang terpisah, yang memonitor pengecualian yang dilemparkan ke dalam, dan jika Promise tiba, berlangganan resolve , dan kemudian restart serat.


Tokoh


Untuk menunjukkan cara kerja pseudo-fiber, kami akan menulis kode rumit ..


Bagan Eksekusi Khas


Mari kita bayangkan bahwa fungsi step sini menulis sesuatu ke konsol dan melakukan kerja keras lainnya selama 20 ms. Dan fungsi walk memanggil step dua kali, mencatat seluruh proses. Di tengah, itu akan menunjukkan apa yang sekarang ditampilkan di konsol. Dan di sebelah kanan adalah keadaan pohon pseudofiber.


$ mol_fiber: tidak ada kuantisasi


Mari kita jalankan kode ini dan lihat apa yang terjadi ..


Eksekusi tanpa kuantisasi


Sejauh ini, semuanya sederhana dan jelas. Pohon serat semu, tentu saja, tidak terlibat. Dan semuanya akan baik-baik saja, tetapi kode ini dieksekusi selama lebih dari 40 ms, yang tidak berharga.


$ mol_fiber: cache terlebih dahulu


Mari kita bungkus kedua fungsi dalam pembungkus khusus yang menjalankannya dalam pseudo-fiber dan lihat apa yang terjadi ..


Cache mengisi


Di sini perlu diperhatikan fakta bahwa untuk setiap tempat memanggil one fungsi di dalam serat walk , serat terpisah dibuat. Hasil dari panggilan pertama di-cache, tetapi alih-alih yang kedua, Promise dilemparkan, karena kami telah menghabiskan waktu kita.


$ mol_fiber: cache kedua


Dilemparkan ke dalam frame pertama, Promise akan secara otomatis diselesaikan pada frame berikutnya, yang akan menyebabkan restart dari walk fiber.


Penggunaan Kembali Cache


Seperti yang Anda lihat, karena restart, kami kembali mengembalikan "start" dan "first done" ke konsol, tetapi "first start" tidak ada lagi, karena ia berada di serat dengan cache yang diisi sebelumnya, karena penangannya lebih tidak dipanggil. Ketika cache serat walk terisi, semua serat yang tertanam dihancurkan, karena eksekusi tidak akan pernah mencapai mereka.


Jadi mengapa first begin mencetak satu kali dan first done dua? Ini semua tentang idempotensi. console.log - operasi non-idempoten, berapa kali Anda menyebutnya, berapa kali akan menambahkan entri ke konsol. Tetapi serat yang mengeksekusi di serat lain idempoten, serat hanya mengeksekusi pegangan pada panggilan pertama, dan pada pengembalian berikutnya segera hasil dari cache, tanpa mengarah ke efek samping tambahan.


$ mol_fiber: idempotence terlebih dahulu


Mari kita bungkus console.log dalam sebuah fiber, sehingga membuatnya idempoten, dan lihat bagaimana program tersebut berperilaku ..


mengisi cache idempoten


Seperti yang Anda lihat, sekarang di pohon serat kami memiliki entri untuk setiap panggilan ke fungsi log .


$ mol_fiber: idempotence kedua


Pada restart berikutnya dari walk fiber, panggilan berulang ke fungsi log tidak lagi mengarah ke panggilan ke console.log nyata, tetapi segera setelah kita mendapatkan eksekusi dari fiber dengan cache kosong, panggilan ke console.log dilanjutkan.


Menggunakan kembali cache idempoten


Harap dicatat bahwa di konsol kami sekarang tidak menampilkan sesuatu yang berlebihan - persis apa yang akan ditampilkan dalam kode sinkron tanpa serat dan kuantifikasi.


$ mol_fiber: break


Bagaimana cara penghitungan interupsi? Di awal kuantum, batas waktu ditetapkan. Dan sebelum memulai setiap serat, diperiksa apakah kita telah mencapainya. Dan jika Anda mencapai, maka Promise bergegas, yang diselesaikan di bingkai berikutnya dan memulai kuantum baru ..


 if( Date.now() > $mol_fiber.deadline ) { throw new Promise( $mol_fiber.schedule ) } 

$ mol_fiber: batas waktu


Batas waktu untuk kuantum mudah diatur. 8 milidetik ditambahkan ke waktu saat ini. Mengapa tepatnya 8, karena ada sebanyak 16 untuk mempersiapkan tembakan? Faktanya adalah kita tidak tahu sebelumnya berapa lama browser harus di-render, jadi kita perlu menyisihkan waktu agar browser berfungsi. Tetapi kadang-kadang terjadi bahwa browser tidak perlu membuat apa pun, dan kemudian dengan 8ms kuanta kita dapat memasukkan kuantum lain ke dalam bingkai yang sama, yang akan memberikan pengemasan padat dari quanta dengan downtime prosesor yang minimal.


 const now = Date.now() const quant = 8 const elapsed = Math.max( 0 , now - $mol_fiber.deadline ) const resistance = Math.min( elapsed , 1000 ) / 10 // 0 .. 100 ms $mol_fiber.deadline = now + quant + resistence 

Tetapi jika kita hanya membuang eksepsi setiap 8ms, maka debugging dengan stop yang dihidupkan akan berubah menjadi cabang kecil neraka. Kami membutuhkan beberapa mekanisme untuk mendeteksi mode debugger ini. Sayangnya, ini hanya dapat dipahami secara tidak langsung: seseorang membutuhkan waktu sekitar satu detik untuk memahami apakah akan melanjutkan eksekusi atau tidak. Dan ini berarti bahwa jika kontrol tidak kembali ke skrip untuk waktu yang lama, maka debugger akan berhenti, atau ada perhitungan yang berat. Untuk duduk di kedua kursi, kami menambah 10% dari waktu yang telah berlalu, tetapi tidak lebih dari 100 ms. Ini tidak terlalu memengaruhi FPS, tetapi mengurangi frekuensi berhenti debugger dengan urutan besarnya karena kuantisasi.


Debug: coba / tangkap


Karena kita berbicara tentang debugging, bagaimana menurut Anda, di mana kode ini berhenti debugger?


 function foo() { throw new Error( 'Something wrong' ) // [1] } try { foo() } catch( error ) { handle( error ) throw error // [2] } 

Sebagai aturan, ia perlu berhenti di mana pengecualian dilemparkan untuk pertama kalinya, tetapi kenyataannya adalah ia berhenti hanya di mana itu dilemparkan terakhir kali, yang biasanya sangat jauh dari tempat itu terjadi. Karena itu, agar tidak menyulitkan debugging, pengecualian tidak boleh ditangkap, melalui try-catch. Tetapi bahkan tanpa pengecualian penanganan itu tidak mungkin.


Debug: acara yang tidak ditangani


Biasanya, runtime menyediakan acara global yang terjadi untuk setiap pengecualian yang tidak tertangkap.


 function foo() { throw new Error( 'Something wrong' ) } window.addEventListener( 'error' , event => handle( event.error ) ) foo() 

Selain ketidaknyamanan, solusi ini memiliki kelemahan sehingga semua pengecualian jatuh di sini dan cukup sulit untuk memahami dari serat dan serat apakah peristiwa itu terjadi.


Debug: Janji


Janji adalah cara terbaik untuk menangani pengecualian.


 function foo() { throw new Error( 'Something wrong' ) } new Promise( ()=> { foo() } ).catch( error => handle( error ) ) 

Fungsi yang diteruskan ke Janji disebut segera, secara serempak, tetapi pengecualian tidak tertangkap dan aman menghentikan debugger di tempat kejadiannya. Beberapa saat kemudian, secara tidak sinkron, ia sudah memanggil penangan kesalahan, di mana kita tahu persis serat mana yang memberi kegagalan dan kegagalan mana. Inilah mekanisme yang digunakan dalam $ mol_fiber.


Jejak tumpukan: bereaksi serat


Mari kita lihat jejak stack yang Anda dapatkan di React Fiber ..


Tumpukan kosong


Seperti yang Anda lihat, kami mendapat banyak nyali Bereaksi. Dari yang berguna di sini, hanya titik terjadinya pengecualian dan nama-nama komponen lebih tinggi dalam hierarki. Tidak banyak.


Jejak tumpukan: $ mol_fiber


Di $ mol_fiber, kami mendapatkan jejak stack yang jauh lebih berguna: tanpa nyali, hanya titik-titik tertentu dalam kode aplikasi yang melaluinya pengecualian.


Strace konten


Ini dicapai melalui penggunaan tumpukan asli, janji-janji dan penghapusan usus secara otomatis. Jika mau, Anda dapat memperluas kesalahan di konsol, seperti di tangkapan layar, dan melihat isi, tetapi tidak ada yang menarik.


$ mol_fiber: handle


Jadi, untuk mengganggu kuantum, Janji dilemparkan.


 limit() { if( Date.now() > $mol_fiber.deadline ) { throw new Promise( $mol_fiber.schedule ) } // ... } 

Tetapi, seperti yang Anda duga, Janji dapat benar-benar apa saja - bagi seorang Fibre, secara umum, tidak masalah apa yang diharapkan: frame berikutnya, penyelesaian pemuatan data atau sesuatu yang lain ..


 fail( error : Error ) { if( error instanceof Promise ) { const listener = ()=> self.start() return error.then( listener , listener ) } // ... } 

Fibre hanya berlangganan untuk menyelesaikan janji dan memulai kembali. Tapi janji melempar dan menangkap secara manual tidak perlu, karena paket itu mencakup beberapa pembungkus yang bermanfaat ..


$ mol_fiber: functions


Untuk mengubah fungsi sinkron apa pun menjadi serat idempoten, cukup bungkus dalam $mol_fiber_func ..


 import { $mol_fiber_func as fiberize } from 'mol_fiber/web' const log = fiberize( console.log ) export const main = fiberize( ()=> { log( getData( 'goo.gl' ).data ) } ) 

Di sini kami membuat console.log idempoten, dan main diajarkan untuk menginterupsi sambil menunggu unduhan.


$ mol_fiber: penanganan kesalahan


Tetapi bagaimana menanggapi pengecualian jika kita tidak ingin menggunakan try-catch ? Kemudian kita dapat mendaftarkan penangan kesalahan dengan $mol_fiber_catch ...


 import { $mol_fiber_func as fiberize , $mol_fiber_catch as onError } from 'mol_fiber' const getConfig = fiberize( ()=> { onError( error => ({ user : 'Anonymous' }) ) return getData( '/config' ).data } ) 

Jika kita mengembalikan sesuatu yang berbeda dari kesalahan di dalamnya, maka itu akan menjadi hasil dari serat saat ini. Dalam contoh ini, jika tidak mungkin untuk mengunduh konfigurasi dari server, fungsi getConfig akan mengembalikan konfigurasi secara default.


$ mol_fiber: metode


Tentu saja, Anda dapat membungkus tidak hanya fungsi, tetapi juga metode menggunakan dekorator ..


 import { $mol_fiber_method as action } from 'mol_fiber/web' export class Mover { @action move() { sendData( 'ya.ru' , getData( 'goo.gl' ) ) } } 

Di sini, misalnya, kami mengunggah data dari Google dan mengunggahnya ke Yandex.


$ mol_fiber: janji


Untuk mengunduh data dari server, cukup untuk mengambil, misalnya, fungsi asinkron fetch dan dengan gerakan pergelangan tangan mengubahnya menjadi sinkron.


 import { $mol_fiber_sync as sync } from 'mol_fiber/web' export const getData = sync( fetch ) 

Implementasi ini baik untuk semua orang, tetapi itu tidak mendukung membatalkan permintaan ketika pohon serat dihancurkan, jadi kita perlu menggunakan API lebih bingung ..


$ mol_fiber: batalkan permintaan


 import { $mol_fiber_async as async } from 'mol_fiber/web' function getData( uri : string ) : Response { return async( back => { var controller = new AbortController(); fetch( uri , { signal : controller.signal } ).then( back( res => res ) , back( error => { throw error } ) , ) return ()=> controller.abort() } ) } 

Fungsi yang diteruskan ke bungkus async disebut hanya sekali dan bungkus back dilewatkan ke sana, di mana Anda perlu membungkus panggilan balik. Karenanya, dalam panggilan balik ini, Anda harus mengembalikan nilainya atau melemparkan pengecualian. Apa pun hasil dari panggilan balik itu, itu juga akan menjadi hasil serat. Harap dicatat bahwa pada akhirnya kami mengembalikan fungsi yang akan dipanggil jika terjadi kerusakan prematur serat.


$ mol_fiber: batalkan respons


Di sisi server, dapat juga berguna untuk membatalkan perhitungan ketika klien jatuh. Mari kita menerapkan pembungkus di atas midleware yang akan membuat serat di mana midleware asli akan berjalan. , , , .


 import { $mol_fiber_make as Fiber } from 'mol_fiber' const middle_fiber = middleware => ( req , res ) => { const fiber = Fiber( ()=> middleware( req , res ) ) req.on( 'close' , ()=> fiber.destructor() ) fiber.start() } app.get( '/foo' , middle_fiber( ( req , res ) => { // do something } ) ) 

$mol_fiber: concurrency


, . , 3 : , , - ..


Permintaan cepat dan lambat


: , . . , , .


$mol_fiber: properties


, ..


Pros:
  • Runtime support isn't required
  • Can be cancelled at any time
  • High FPS
  • Concurrent execution
  • Debug friendly
  • ~ 3KB gzipped


Cons:
  • Instrumentation is required
  • All code should be idempotent
  • Longer total execution

$mol_fiber โ€” , . โ€” , . , , . , , , , . , . .


Links



Call back


Umpan balik


: , , )


: , .


: . , .


: . , . , .


: , . , )


: , .


: - . , , .


: . , , .


: , . 16ms, ? 16 8 , 8, . , . , ยซยป.


: โ€” . Terima kasih


: . , . !


: , . .


: , , , , , / , .


: , .


: .


: , . mol.


: , , . , , , .


: .


: , . , $mol, , .


: , , . โ€” . .


: - , .


: $mol , . (pdf, ) , .


: , . , .


: , ) .


: . .


: In some places I missed what the reporter was saying. The conversation was about how to use the "Mola" library and "why?". But how it works remains a mystery for me.To smoke an source code is for the overhead.


: , .


: . , . . .


: : . - (, ). , : 16?


So-so : Saya tidak bekerja dengan serat. Pada laporan itu saya mendengar teori kerja serat. Tapi saya benar-benar tidak tahu cara menggunakan mol_fiber di rumah ... Contoh kecil sangat bagus, tetapi bagaimana ini dapat diterapkan pada aplikasi besar dengan 30fps untuk mempercepat hingga 60fps - tidak ada pemahaman. Sekarang, jika penulis lebih memperhatikan hal ini dan kurang mendesain modul internal - peringkat akan lebih tinggi.

Source: https://habr.com/ru/post/id413791/


All Articles