@@ -1218,6 +1218,247 @@ pub const Lua = struct {
12181218 pub inline fn setAssertHandler (handler : AssertHandler ) void {
12191219 assert .setAssertHandler (handler );
12201220 }
1221+
1222+ /// Garbage collector control methods.
1223+ ///
1224+ /// Luau uses an incremental garbage collector that performs work in small increments
1225+ /// without stopping the world. These methods provide fine-grained control over GC behavior.
1226+ pub const GC = struct {
1227+ lua : Lua ,
1228+
1229+ /// Stop the garbage collector.
1230+ ///
1231+ /// Disables automatic garbage collection. Memory will continue to be allocated
1232+ /// but no garbage collection cycles will run until `restart()` is called.
1233+ /// Use with caution as this can lead to excessive memory usage.
1234+ ///
1235+ /// Example:
1236+ /// ```zig
1237+ /// const gc = lua.gc();
1238+ /// gc.stop(); // GC is now disabled
1239+ /// // ... do memory-intensive work
1240+ /// gc.restart(); // Re-enable GC
1241+ /// ```
1242+ pub fn stop (self : GC ) void {
1243+ _ = self .lua .state .gc (.stop , 0 );
1244+ }
1245+
1246+ /// Restart the garbage collector.
1247+ ///
1248+ /// Re-enables automatic garbage collection after it was stopped with `stop()`.
1249+ /// The garbage collector will resume its normal incremental collection cycles.
1250+ ///
1251+ /// Example:
1252+ /// ```zig
1253+ /// const gc = lua.gc();
1254+ /// gc.stop();
1255+ /// // ... do work with GC disabled
1256+ /// gc.restart(); // GC is active again
1257+ /// ```
1258+ pub fn restart (self : GC ) void {
1259+ _ = self .lua .state .gc (.restart , 0 );
1260+ }
1261+
1262+ /// Force a full garbage collection cycle.
1263+ ///
1264+ /// Performs a complete garbage collection pass, freeing all unreachable objects.
1265+ /// This is more thorough than the incremental collection and may cause a brief pause.
1266+ /// Useful for reclaiming memory at application boundaries or after large operations.
1267+ ///
1268+ /// Example:
1269+ /// ```zig
1270+ /// const gc = lua.gc();
1271+ /// // After loading large amounts of data
1272+ /// gc.collect(); // Free any unused memory
1273+ /// ```
1274+ pub fn collect (self : GC ) void {
1275+ _ = self .lua .state .gc (.collect , 0 );
1276+ }
1277+
1278+ /// Get the total memory usage in kilobytes.
1279+ ///
1280+ /// Returns the total amount of memory currently used by the Lua state,
1281+ /// including all Lua objects, strings, tables, functions, etc.
1282+ /// The value is returned in kilobytes (1024 bytes).
1283+ ///
1284+ /// Example:
1285+ /// ```zig
1286+ /// const gc = lua.gc();
1287+ /// const memory_kb = gc.count();
1288+ /// std.debug.print("Lua memory usage: {} KB\n", .{memory_kb});
1289+ /// ```
1290+ ///
1291+ /// Returns: Memory usage in kilobytes
1292+ pub fn count (self : GC ) i32 {
1293+ return self .lua .state .gc (.count , 0 );
1294+ }
1295+
1296+ /// Get the fractional part of memory usage.
1297+ ///
1298+ /// Returns the remainder of memory usage in bytes after the kilobyte count.
1299+ /// Combined with `count()`, this gives precise memory usage:
1300+ /// `total_bytes = count() * 1024 + countBytes()`
1301+ ///
1302+ /// Example:
1303+ /// ```zig
1304+ /// const gc = lua.gc();
1305+ /// const kb = gc.count();
1306+ /// const bytes = gc.countBytes();
1307+ /// const total_bytes = kb * 1024 + bytes;
1308+ /// std.debug.print("Precise memory usage: {} bytes\n", .{total_bytes});
1309+ /// ```
1310+ ///
1311+ /// Returns: Additional bytes beyond the kilobyte count (0-1023)
1312+ pub fn countBytes (self : GC ) i32 {
1313+ return self .lua .state .gc (.countb , 0 );
1314+ }
1315+
1316+ /// Check if the garbage collector is currently running.
1317+ ///
1318+ /// Returns `true` if automatic garbage collection is enabled and active,
1319+ /// `false` if it has been stopped with `stop()` or is otherwise disabled.
1320+ ///
1321+ /// Example:
1322+ /// ```zig
1323+ /// const gc = lua.gc();
1324+ /// if (gc.isRunning()) {
1325+ /// std.debug.print("GC is active\n");
1326+ /// } else {
1327+ /// std.debug.print("GC is stopped\n");
1328+ /// }
1329+ /// ```
1330+ ///
1331+ /// Returns: `true` if GC is running, `false` otherwise
1332+ pub fn isRunning (self : GC ) bool {
1333+ return self .lua .state .gc (.isrunning , 0 ) != 0 ;
1334+ }
1335+
1336+ /// Perform a single incremental garbage collection step.
1337+ ///
1338+ /// Runs one step of the incremental garbage collector. The `size` parameter
1339+ /// controls how much work to do in this step (larger values = more work).
1340+ /// Returns `true` if the GC cycle completed, `false` if more steps are needed.
1341+ ///
1342+ /// This is useful for manual control over GC timing in performance-critical code.
1343+ ///
1344+ /// Example:
1345+ /// ```zig
1346+ /// const gc = lua.gc();
1347+ /// gc.stop(); // Disable automatic GC
1348+ ///
1349+ /// while (!gc.step(100)) {
1350+ /// // GC cycle not complete, do some work
1351+ /// // ... application work ...
1352+ /// }
1353+ /// // GC cycle completed
1354+ /// ```
1355+ ///
1356+ /// Parameters:
1357+ /// - `size`: Amount of work to perform (typically 100-1000)
1358+ ///
1359+ /// Returns: `true` if GC cycle completed, `false` if more work remains
1360+ pub fn step (self : GC , size : i32 ) bool {
1361+ return self .lua .state .gc (.step , size ) != 0 ;
1362+ }
1363+
1364+ /// Set the garbage collection goal.
1365+ ///
1366+ /// Controls when the next GC cycle should start based on memory usage.
1367+ /// The goal is specified as a percentage of current memory usage.
1368+ /// For example, a goal of 200 means GC will start when memory usage
1369+ /// doubles from the current level.
1370+ ///
1371+ /// Lower values trigger GC more frequently (less memory usage, more CPU overhead).
1372+ /// Higher values trigger GC less frequently (more memory usage, less CPU overhead).
1373+ ///
1374+ /// Example:
1375+ /// ```zig
1376+ /// const gc = lua.gc();
1377+ /// gc.setGoal(150); // Start GC when memory increases by 50%
1378+ /// ```
1379+ ///
1380+ /// Parameters:
1381+ /// - `goal`: GC trigger threshold as percentage (typically 100-300)
1382+ ///
1383+ /// Returns: Previous goal value
1384+ pub fn setGoal (self : GC , goal : i32 ) i32 {
1385+ return self .lua .state .gc (.setgoal , goal );
1386+ }
1387+
1388+ /// Set the garbage collection step multiplier.
1389+ ///
1390+ /// Controls how much work the GC does in each incremental step relative
1391+ /// to memory allocation. Higher values make GC more aggressive (more CPU
1392+ /// overhead but lower memory usage), lower values make it less aggressive.
1393+ ///
1394+ /// The default value is typically around 200. Values between 100-500 are common.
1395+ ///
1396+ /// Example:
1397+ /// ```zig
1398+ /// const gc = lua.gc();
1399+ /// gc.setStepMul(300); // Make GC more aggressive
1400+ /// ```
1401+ ///
1402+ /// Parameters:
1403+ /// - `stepmul`: Step multiplier (typically 100-500)
1404+ ///
1405+ /// Returns: Previous step multiplier value
1406+ pub fn setStepMul (self : GC , stepmul : i32 ) i32 {
1407+ return self .lua .state .gc (.setstepmul , stepmul );
1408+ }
1409+
1410+ /// Set the garbage collection step size.
1411+ ///
1412+ /// Controls the size of each incremental GC step in bytes. Larger step sizes
1413+ /// mean fewer but larger GC pauses, smaller step sizes mean more frequent
1414+ /// but shorter pauses.
1415+ ///
1416+ /// This fine-tunes the incremental GC behavior for specific performance needs.
1417+ ///
1418+ /// Example:
1419+ /// ```zig
1420+ /// const gc = lua.gc();
1421+ /// gc.setStepSize(1024); // 1KB per GC step
1422+ /// ```
1423+ ///
1424+ /// Parameters:
1425+ /// - `stepsize`: Size of each GC step in bytes
1426+ ///
1427+ /// Returns: Previous step size value
1428+ pub fn setStepSize (self : GC , stepsize : i32 ) i32 {
1429+ return self .lua .state .gc (.setstepsize , stepsize );
1430+ }
1431+ };
1432+
1433+ /// Get a garbage collector control interface.
1434+ ///
1435+ /// Returns a GC control object that provides methods for managing Luau's
1436+ /// incremental garbage collector. Use this to monitor memory usage,
1437+ /// tune GC performance, or manually control collection timing.
1438+ ///
1439+ /// Example:
1440+ /// ```zig
1441+ /// const lua = try Lua.init(null);
1442+ /// defer lua.deinit();
1443+ ///
1444+ /// const gc = lua.gc();
1445+ ///
1446+ /// // Check current memory usage
1447+ /// const memory_kb = gc.count();
1448+ /// std.debug.print("Memory usage: {} KB\n", .{memory_kb});
1449+ ///
1450+ /// // Force garbage collection
1451+ /// gc.collect();
1452+ ///
1453+ /// // Check memory after collection
1454+ /// const memory_after = gc.count();
1455+ /// std.debug.print("Memory after GC: {} KB\n", .{memory_after});
1456+ /// ```
1457+ ///
1458+ /// Returns: GC control interface
1459+ pub fn gc (self : Self ) GC {
1460+ return GC { .lua = self };
1461+ }
12211462};
12221463
12231464const expect = std .testing .expect ;
@@ -1390,3 +1631,130 @@ test "struct and array integration" {
13901631 const points_length = try lua .eval ("return #config.points" , .{}, i32 );
13911632 try expectEq (points_length , 2 );
13921633}
1634+
1635+ test "garbage collector operations" {
1636+ const lua = try Lua .init (& std .testing .allocator );
1637+ defer lua .deinit ();
1638+
1639+ lua .openLibs ();
1640+
1641+ const gc = lua .gc ();
1642+
1643+ // Test that GC is initially running
1644+ try expect (gc .isRunning ());
1645+
1646+ // Test memory counting
1647+ const initial_memory = gc .count ();
1648+ const initial_bytes = gc .countBytes ();
1649+ try expect (initial_memory >= 0 );
1650+ try expect (initial_bytes >= 0 and initial_bytes < 1024 );
1651+
1652+ // Create some objects to increase memory usage
1653+ const globals = lua .globals ();
1654+ try globals .set ("test_table" , lua .createTable (.{ .rec = 100 }));
1655+
1656+ _ = try lua .eval (
1657+ \\for i = 1, 100 do
1658+ \\ test_table[i] = "string number " .. i
1659+ \\end
1660+ , .{}, void );
1661+
1662+ // Memory should have increased
1663+ const after_alloc_memory = gc .count ();
1664+ try expect (after_alloc_memory >= initial_memory );
1665+
1666+ // Test stopping and restarting GC
1667+ gc .stop ();
1668+ try expect (! gc .isRunning ());
1669+
1670+ gc .restart ();
1671+ try expect (gc .isRunning ());
1672+
1673+ // Test forcing garbage collection
1674+ gc .collect ();
1675+ const after_collect_memory = gc .count ();
1676+
1677+ // Memory might be same or less after collection
1678+ // (depends on what was actually collectible)
1679+ try expect (after_collect_memory >= 0 );
1680+
1681+ // Test GC stepping
1682+ gc .stop ();
1683+ try expect (! gc .isRunning ());
1684+
1685+ // Create more garbage
1686+ _ = try lua .eval (
1687+ \\local temp = {}
1688+ \\for i = 1, 50 do
1689+ \\ temp[i] = {}
1690+ \\ for j = 1, 10 do
1691+ \\ temp[i][j] = "temp string " .. i .. "," .. j
1692+ \\ end
1693+ \\end
1694+ \\temp = nil
1695+ , .{}, void );
1696+
1697+ // Perform stepped collection
1698+ var steps : u32 = 0 ;
1699+ while (! gc .step (100 ) and steps < 10 ) {
1700+ steps += 1 ;
1701+ }
1702+
1703+ // Should have completed within reasonable steps
1704+ try expect (steps < 10 );
1705+
1706+ // Test GC parameter setting
1707+ const old_goal = gc .setGoal (150 );
1708+ try expect (old_goal > 0 );
1709+
1710+ const old_stepmul = gc .setStepMul (250 );
1711+ try expect (old_stepmul > 0 );
1712+
1713+ const old_stepsize = gc .setStepSize (2048 );
1714+ try expect (old_stepsize > 0 );
1715+
1716+ // Restore original parameters
1717+ _ = gc .setGoal (old_goal );
1718+ _ = gc .setStepMul (old_stepmul );
1719+ _ = gc .setStepSize (old_stepsize );
1720+
1721+ gc .restart ();
1722+ try expect (gc .isRunning ());
1723+ }
1724+
1725+ test "gc memory measurement precision" {
1726+ const lua = try Lua .init (& std .testing .allocator );
1727+ defer lua .deinit ();
1728+
1729+ const gc = lua .gc ();
1730+
1731+ // Test precise memory measurement
1732+ const kb = gc .count ();
1733+ const bytes = gc .countBytes ();
1734+ const total_bytes = kb * 1024 + bytes ;
1735+
1736+ try expect (kb >= 0 );
1737+ try expect (bytes >= 0 and bytes < 1024 );
1738+ try expect (total_bytes >= 0 );
1739+
1740+ // Allocate a known amount and verify memory increases
1741+ const globals = lua .globals ();
1742+ const large_table = lua .createTable (.{ .arr = 1000 });
1743+ defer large_table .deinit ();
1744+
1745+ try globals .set ("large_table" , large_table );
1746+
1747+ // Fill table with data
1748+ _ = try lua .eval (
1749+ \\for i = 1, 1000 do
1750+ \\ large_table[i] = i
1751+ \\end
1752+ , .{}, void );
1753+
1754+ const kb_after = gc .count ();
1755+ const bytes_after = gc .countBytes ();
1756+ const total_bytes_after = kb_after * 1024 + bytes_after ;
1757+
1758+ // Memory should have increased significantly
1759+ try expect (total_bytes_after > total_bytes );
1760+ }
0 commit comments