Sunday, November 20, 2022

Slashes in Lua "require" function: Use periods/dots!

This is somewhat a kind of misunderstood but I hope this blog post will resolve this once for all. I often saw people using Lua require like this

local gamera = require("libs/gamera")
local nvec = require("libs/nvec")
-- Rest of the code

At first glance, there's nothing wrong right? No. Using slashes in require works because it's an accident, but no this is not a happy accident. What you should do is:

local gamera = require("libs.gamera")
local nvec = require("libs.nvec")
-- Rest of the code

So, why the dots there? Because require is not expecting path, it's expecting module name. To explain why, first let's take a tour to Programming in Lua Section 8.1, with important text marked as bold.

The path used by require is a little different from typical paths. Most programs use paths as a list of directories wherein to search for a given file. However, ANSI C (the abstract platform where Lua runs) does not have the concept of directories. Therefore, the path used by require is a list of patterns, each of them specifying an alternative way to transform a virtual file name (the argument to require) into a real file name.

So, it's clear that require does not expect a path to filename, but it expects module name (or virtual file name; we'll use module name from now on). To understand how Lua transform the module name to actual path that Lua will try to load, it's important to know about package.loaders.

package.loaders is an array of function which tries to load a module based on module name passed by require. The Lua 5.1 manual has more information about this, but I'll try to explain it as simple as possible. In Lua 5.1 (and LuaJIT), there are 4 loaders in this entry but I'll only explain the first 3, tried in this order:

  1. Checks for existing module loader in package.preload table with the module name (passed from require) as the table key, such that when called, it loads the module. If it's non-nil, then the value is returned.
  2. Replace all dots in module name to OS-specific directory separator (we'll call this file path). Then for each semicolon-separated path specified in package.path, substitute question mark with the file path then try to open that as Lua file. If it's loaded successfully then the function chunk is returned.
  3. For this, it needs 2 components: file path and entry point. Replace all dots in module name to OS-specific directory separator to get file path, and replace all dots in module name to underscore with luaopen_ prepended to get entry point. Then for each semicolon-separated path specified in package.cpath (note the "c" in cpath), it tries to load said file as shared library (or DLL in Windows), then returns Lua C function with specified entry point inside the shared library.
  4. It's all-in-one loader, doesn't matter in our case. 

If you're still confused, this pseudo-Python code will help you know how it works.

import os

package.preload = dict()
package.path = "?.lua;path/to/?.lua"
package.cpath = "?.dll;?.so;path/to/?.dll;path/to/?.so"

def loader_1(modname):
	module = pacakge.preload.get(modname)
	if module is not None:
		return module
	return f"no field package.preload['{module}']"

def loader_2(modname):
	file_path = modname.replace(".", os.sep)
	tested = []
	for path in package.path.split(";"):
		file_name = path.replace("?", file_path)
		chunk = load_lua_file(file_name)
		if chunk is not None:
			return chunk
		tested.append(f"no file '{file_name}'")
	return "\n".join(tested)

def loader_3(modname):
	file_path = modname.replace(".", os.sep)
	entry_point = "luaopen_" + modname.replace(".", "_")
	tested = []
	for path in package.cpath.split(";"):
		file_name = path.replace("?", file_path)
		module = open_shared_library(file_name) # dlopen or LoadLibraryA
		if module:
			symbol = get_symbol(module, entry_point) # dlsym or GetProcAddress
			if symbol is not None:
				return make_symbol_callable(symbol)
		close_shared_library(module) # dlclose or FreeLibrary
		tested.append(f"no file '{file_name}'")
	return "\n".join(tested)

If that's clear enough, then stop reading and start fixing your require by replacing slashes with dots!

Saturday, October 8, 2022

The Case of Rare Laptop SKU: Lenovo Ideapad Gaming 3 15ACH6

 I bought a gaming laptop a month ago (as of writing this blog post). The laptop name is in the title, with these key-selling specs:

  • AMD Ryzen 5 5600H
  • RTX 3060 Laptop (90W)
  • 16GB of DDR-3200 RAM at dual-channel, upgradeable
  • 1920x1080 165Hz display, 100% sRGB
  • 512GB NVMe SSD (which I later upgraded with additional 512GB)

Honestly getting RTX 3060 laptop for cheap (I really mean "cheap", my touchscreen laptop is more expensive) is a blessing. The cooling are also good and I never able to reach 80 degrees Celsius when I use it for gaming (70 is the highest). Now there's just a slight issue: This is a rare SKU.

Why? There are no reviews of this laptop with this GPU. If you look at internet, all reviews of this laptop is the ones with RTX 3050 or 3050 Ti. None of them with 3060. This means there's no way for me to have expectation on the performance and there's no way for me to evaluate the pros and the cons of this variant. I'm on my own.

Lenovo published Product Specifications Reference for this laptop but even some of the information there does not reflect 100% the ones I have. Some of those are:

  • The sheet wrote one of the SSD slots runs at PCIe 3.0x2, but on my unit it runs on 3.0x4 on both slots, checked with Crystal Disk Info.
  • The sheet wrote one of the SSD needs 2242 form-factor (this one is running at 3.0x4), but this is not the case for my unit. They both accepts 2280 form-factor. I mistakenly bought 2242 SSD without knowing because I assume their PSR are correct.

Fortunately I'm relieved to find out that there are many advantages of getting this variant. For example, the RTX 3050 with maximum TGP still beaten by RTX 3060 with lowest TGP and the fact that 3060 Laptop it's almost on-par with the desktop variant (3050, 3070, and 3080 has significant difference). Furthermore the higher VRAM (6GB instead of 3050 4GB) means I can barely run Stable Diffusion locally

1girl long_hair blue_aqua_hair purple_eyes red_glasses blunt_bangs straight_hair black_blazer red_ribbon black_skirt school_girl cat_ears

... to generate waifus.