Penyimpanan Status Pada Fungsi C
27.3 Penyimpanan Status Pada Fungsi C
Seringkali, fungsi C perlu untuk menyimpan beberapa data non-lokal, yaitu data yang mengalami invocation. Pada C, kita biasanya menggunakan variable global atau statis tergantung kebutuhannya. Saat anda sedang memprogram fungsi library untuk Lua, bagaimanapun, variable global dan statis bukanlah suatu pendekatan yang baik. Pertama, anda tidak dapat menyimpan suatu Seringkali, fungsi C perlu untuk menyimpan beberapa data non-lokal, yaitu data yang mengalami invocation. Pada C, kita biasanya menggunakan variable global atau statis tergantung kebutuhannya. Saat anda sedang memprogram fungsi library untuk Lua, bagaimanapun, variable global dan statis bukanlah suatu pendekatan yang baik. Pertama, anda tidak dapat menyimpan suatu
Suatu pendekatan alternative adalah menyimpan nilai-nilai seperti itu ke dalam variable global Lua. Pendekatan ini memecahkan dua masalah sebelumnya. Variable global Lua menyimpan beberapa nilai Lua dan masing-masing status yang berdiri sendiri memiliki letaknya sendiri pada variable global. Bagaimanapun, ini bukanlah suatu solusi yang selalu memuaskan, karena kode Lua dapat bercampur dengan variable global tersebut dan karena itu persetujuan integritas dari data C. Untuk menghindari masalah ini, Lua menawarkan suatu table terpisah, yang disebut registry, bahwa kode C dapat bebas digunakan, tapi kode Lua tidak dapat diakses.
27.3.1 Registry
Registry selalu ditempatkan pada sebuah pseudo_index, yang nilainya ditentukan oleh LUA_REGISTRYINDEX. Sebuah pseudo_index menyerupai sebuah indeks di dalam stack, kecuali jika dihubungkan dengan nilai, pseudo_index tidak berada di dalam stack. Kebanyakan fungsi pada Lua API yang menerima indeks sebagai argument juga menerima indeks pseudo--- pengecualian yang terjadi pada fungsi-fungsi yang memanipulasi stack itu sendiri, yaitu lua_remove dan lua_insert. Suatu kondisi untuk mendapatkan sebuah nilai yang disimpan dengan kunci “key” pada registry, anda dapat menggunakan kode sebagai berikut :
lua_pushstring(L, "Key"); lua_gettable(L, LUA_REGISTRYINDEX);
Registry tersebut merupakan sebuah tabel Lua yang tetap (regular). Seperti anda dapat mengelompokkannya dengan beberapa nilai Lua tetapi nil. Bagaimanapun, karena semua library C membagi registry yang sama, anda harus memilih dengan memperhatikan nilai-nilai apa yang anda gunakan sebagai key, untuk menghindari benturan. Suatu metode bulletproof digunakan sebagai alamat key dari sebuah variabel statis di dalam kode anda. Editor penghubung C menjamin bahwa key ini unik diantara semua library. Untuk menggunakan pilihan ini, anda memerlukan fungsi lua_pushlightuserdata, yang mendorong sebuah nilai yang mewakili suatu pointer C pada stack Lua. Kode tersebut memperlihatkan bagaimana menyimpan dan mendapatkan kembali sebuah bilangan dari registry menggunakan metode ini :
/* variable with an unique address */ static const char Key = 'k';
/* store a number */ lua_pushlightuserdata(L, (void *)&Key); /* push address */ lua_pushnumber(L, myNumber); /* push value */ /* registry[&Key] = myNumber */ lua_settable(L, LUA_REGISTRYINDEX);
/* retrieve a number */ lua_pushlightuserdata(L, (void *)&Key); /* push address */ lua_gettable(L, LUA_REGISTRYINDEX); /* retrieve value */ myNumber = lua_tonumber(L, -1); /* convert to number */
Kita akan membicarakan tentang light userdata secara lebih terinci pada bagian 28.5. Tentu saja, anda juga dapat menggunakan string sebagai key di dalam registry, selama anda
memilih nama-nama unik. Key string sangat berguna saat anda ingin memperbolehkan library lain memilih nama-nama unik. Key string sangat berguna saat anda ingin memperbolehkan library lain
27.3.2 References
Anda tidak seharusnya menggunakan bilangan sebagai key pada registry, karena key seperti itu disediakan untuk system reference. System ini terdiri dari sepasang fungsi di dalam auxiliary library yang mengizinkan anda untuk menyimpan nilai pada registry tanpa mencemaskan tentang bagaimana menciptakan nama-nama unik. (Sebenarnya, fungsi-fungsi tersebut dapat melakukan tindakan pada table manapun, tapi fungsi-fungsi tersebut biasanya digunakan dengan registry).
Panggilan tersebut adalah
int r = luaL_ref(L, LUA_REGISTRYINDEX);
mengeluarkan sebuah nilai dari stack, dan menyimpannya ke dalam registry dengan sebuah key integer yang masih baru, dan mengembalikan key tersebut. Kita menyebut key ini sebagai suatu reference .
Sebagai nama yang tersirat, kita menggunakan reference utama saat kita perlu untuk menyimpan sebuah reference ke sebuah nilai Lua di dalam suatu struktur C. Seperti yang telah kita lihat, kita tidak perlu menyimpan pointer ke string Lua di luar dari fungsi C yang diterima kembali oleh mereka. Lebih dari itu, bahkan Lua tidak menawarkan pointer ke objek lainnya, seperti table atau fungsi. Jadi, kita tidak dapat menuju ke objek Lua melalui pointer. Sebagai gantinya, saat kita memerlukan pointer tersebut, kita menciptakan sebuah reference dan menyimpannya pada C.
Untuk mendorong nilai yang dihubungkan dengan sebuah reference r ke dalam stack, kita dapat menulis secara sederhana :
lua_rawgeti(L, LUA_REGISTRYINDEX, r);
Akhirnya, untuk melepaskan nilai dan reference, kita memanggil
luaL_unref(L, LUA_REGISTRYINDEX, r);
Setelah panggilan tersebut, luaL_ref dapat mengembalikan nilai dalam r sebagai sebuah reference baru. Sistem reference memperlakukan nil sebagai sebuah kasus khusus. Kapan pun anda memanggil luaL_ref untuk sebuah nilai nil, luaL_ref tidak menciptakan sebuah reference baru, tetapi sebagai gantinya mengembalikan reference yang tetap yaitu LUA_REFNIL. Panggilan tersebut
luaL_unref(L, LUA_REGISTRYINDEX, LUA_REFNIL);
tidak memiliki efek, sedangkan tidak memiliki efek, sedangkan
mendorong sebuah nil, seperti yang diharapkan. Sistem reference juga mendefinisikan LUA_NOREF yang bernilai tetap, yang merupakan sebuah bilangan bulat (integer) yang berbeda dari reference yang valid. Hal ini berguna untuk memberi tanda pada reference yang invalid. Seperti LUA_REFNIL, usaha apa pun untuk mendapatkan kembali LUA_NOREF mengembalikan nil dan usaha apa pun untuk melepaskannya tidak memiliki efek.
27.3.3 Upvalues
Selagi registry mengimplementasikan nilai global, mekanisme upvalue mengimplementasi suatu vaiabel statis dari C yang bernilai sama (ekuivalen), yang terlihat hanya di dalam fungsi partikular. Setiap anda membuat sebuah fungsi C yang baru pada Lua, anda dapat menghubungkannya dengan beberapa bilangan dari upvalue, setiap upvalue dapat menahan suatu nilai Lua yang tunggal. Kemudian, saat fungsi dipanggil, fungsi tersebut memiliki akses bebas untuk beberapa nilai dari upvalue tersebut, menggunakan indeks pseudo.
Kita menyebut hubungan dari suatu fungsi C dengan upvalue-nya sebagai sebuah closure (penutup). Ingatlah bahwa, pada kode Lua, sebuah closure merupakan suatu fungsi yang menggunakan variabel-variabel lokal dari suatu fungsi luar. Suatu closure C merupakan suatu perkiraan kepada sebuah closure Lua. Satu fakta menarik tentang closure adalah anda dapat membuat closure yang berbeda menggunakan kode fungsi yang sama, tetapi dengan upvalue yang berbeda.
Untuk melihat contoh yang sederhana, mari kita membuat sebuah fungsi newCounter di dalam C. (Kita telah mendefinisikan fungsi yang sama pada Lua, didalam bagian 6.1). Fungsi ini merupakan sebuah pabrik fungsi. Fungsi ini mengembalikan sebuah fungsi newCounter setiap dipanggil. Walaupun semua counter membagi kode C yang sama, masing-masing menyimpan counter -nya sendiri. Pabrik fungsi tersebut adalah sebagai berikut :
/* forward declaration */ static int counter (lua_State *L);
int newCounter (lua_State *L) { lua_pushnumber(L, 0); lua_pushcclosure(L, &counter, 1); return 1;
Fungsi key di sini adalah lua_pushcclosure, yang membuat sebuah fungsi closure yang baru pada C. Argument keduanya merupakan fungsi dasar ( counter, di dalam contoh) dan yang ketiga merupakan bilangan dari upvalue (1, di dalam contoh). Sebelum membuat sebuah closure yang baru, kita harus mendorong nilai awal untuk upvalue-nya pada stack. Di dalam contoh, kita mendorong angka 0 sebagai nilai awal untuk upvalue tunggal. Seperti yang diharapkan, lua_pushcclosure meninggalkan closure baru pada stack, jadi closure tersebut sudah siap untuk dikembalikan sebagai hasil dari newCounter.
Sekarang, mari kita lihat definisi dari counter :
static int counter (lua_State *L) { double val = lua_tonumber(L, lua_upvalueindex(1)); static int counter (lua_State *L) { double val = lua_tonumber(L, lua_upvalueindex(1));
Di sini, fungsi key-nya adalah lua_upvalueindex (yang sebenarnya adalah suatu makro), yang memproduksi pseudo-index dari sebuah upvalue. Lagi-lagi, pseudo-index ini menyerupai beberapa indeks stack, tetapi pseudo-index tidak dapat tinggal di dalam stack. Ekspresi lua_upvalueindex(1) mengacu pada indeks dari upvalue pertama dari fungsi itu. Jadi, lua_tonumber pada fungsi counter menerima kembali nilai yang umum dari upvalue pertama sebagai sebuah bilangan. Kemudian, fungsi counter mendorong nilai baru ++val, membuat sebuah copy darinya dan menggunakan satu copy tersebut untuk memindahkan upvalue dengan nilai yang baru. Akhirnya, fungsi tersebut mengembalikan copy lainnya sebagai nilai kembali.
Tidak seperti closure pada Lua, closure pada C tidak dapat membagi upvalue. Setiap closure memiliki letaknya sendiri. Bagaimanapun, kita tidak dapat mengatur upvalue dari fungsi yang berbeda untuk menuju ke sebuah table umum, jadi table tersebut menjadi sebuah tempat umum dimana fungsi-fungsi tersebut dapat membagi data.