; (function() { // _ _____ ____ _ // | | | ____/ ___| (_)___ // | | | _| \___ \ | / __| // | |___| |___ ___) | | \__ \ // |_____|_____|____(_)/ |___/ // ---------------------------- // LES.js (Last rEcently uSed) // ---------------------------- // A Small, lightweight, queue-based // Garbage Collector for Gun // Originally By: Collin Conrad (@masterex1000) //NOTE: set to false is running from file in YOUR code var USELOCALGUN = true; //NOTE: adds some debug messages var DEBUG = false; var Gun = (typeof window !== "undefined") ? window.Gun : (USELOCALGUN ? require('../gun') : require("gun")); var ev = {}; var empty = {}; Gun.on('opt', function(root) { this.to.next(root); if (root.once) return; if (typeof process == 'undefined') return var mem = process.memoryUsage; if (!mem) //exit because we are in the browser return; //Figure out the most amount of memory we can use. TODO: make configurable? ev.max = parseFloat(root.opt.memory || process.env.WEB_MEMORY || 512) * 0.8; var nodes = {}; //checks if the node already exists var nodesArray = []; //used to easily sort everything and store info about the nodes var memoryUpdate = 0; // last time we printed the current memory stats var check = function() { ev.used = mem().rss / 1024 / 1024; //Contains the amt. of used ram in MB setTimeout(function() { // So we can handle requests etc. before we start collecting GC(ev.used / ev.max); // Calculate the memory ratio, and execute the garbage collector }, 1); } setInterval(check, 1000); // set the garbage collector to run every second, TODO: make configurable //Executed every time a node gets modifyed root.on("put", function(e) { var ctime = Date.now(); var souls = Object.keys(e.put || empty); for (var i = 0; i < souls.length; i++) { enqueueNode(souls[i], ctime); } }); //Adds a soul the garbage collectors "freeing" queue function enqueueNode(soul, ctime) { if (nodes[soul] == true) { //The node already exists in the queue var index = nodesArray.findIndex(function(e) { return e[0] === soul; }); if (index == -1) { console.err("Something happened and the node '" + soul + "' won't get garbage collection unless the value is updated agian"); return; } else { nodesArray.splice(index, 1); // remove the existing ref. nodesArray.push([soul, ctime]); // push the new instance } } else { nodesArray.push([soul, ctime]); nodes[soul] = true; } } //The main garbage collecting routine function GC(memRatio) { var curTime = Date.now(); // get the current time if (curTime - memoryUpdate >= 5000) { console.log("|GC| %s | Current Memory Ratio: %d | Current Ram Usage %sMB | Nodes in Memory %s", new Date().toLocaleString(), round(memRatio, 2), round(ev.used, 2), Object.keys(root.graph || empty).length); memoryUpdate = curTime; } var freed = 0; while (nodesArray.length > 0) { var soul = nodesArray[0][0]; var nts = nodesArray[0][1]; if (DEBUG) console.log("Soul: " + soul + " | Remove Importance: " + calcRemoveImportance(nts, curTime, memRatio) + " | Memory Ratio: " + memRatio + " | Time Existed: " + (curTime - nts) / 1000); if (calcRemoveImportance(nodesArray[0][1], curTime, memRatio) >= 100) { root.gun.get(nodesArray[0][0]).off(); //Remove the node delete nodes[nodesArray[0][0]]; // remove the lookup value nodesArray.splice(0, 1); freed++; } else break; } if (freed > 0) console.log("|GC| Removed %s nodes in %s seconds-----------------------------------------------------------------", freed, (Date.now() - curTime) * 0.001); } //Generates a number that, after it hits a threshold, the node gets removed function calcRemoveImportance(timestamp, ctime, memoryUsageRatio) { var time = (ctime - timestamp) * 0.001; return time * 10 * (memoryUsageRatio * memoryUsageRatio) } function round(value, decimals) { //a basic rounding function return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals); } }); }());