Anakunda / libLocks

// ==UserScript==
// ==UserLibrary==
// @name         libLocks
// @namespace    https://openuserjs.org/users/Anakunda
// @version      1.00
// @author       Wizcorp Open Source
// @license      MIT
// @copyright    2021, Wizcorp Open Source (https://github.com/Wizcorp)
// @description  https://github.com/Wizcorp/locks
// @exclude      *
// ==/UserScript==
// ==/UserLibrary==

function CondVariable(initialValue) {
	this._value = initialValue;
	this._waiting = [];
}

function condToFunc(cond) {
	if (typeof cond === 'function') {
		return cond;
	}

	if (typeof cond === 'number' || typeof cond === 'boolean' || typeof cond === 'string') {
		return function (value) {
			return value === cond;
		};
	}

	if (cond && typeof cond === 'object' && cond instanceof RegExp) {
		return function (value) {
			return cond.test(value);
		};
	}

	throw new TypeError('Unknown condition type: ' + (typeof cond));
}

CondVariable.prototype.get = function () {
	return this._value;
};

CondVariable.prototype.wait = function (cond, cb) {
	var test = condToFunc(cond);

	if (test(this._value)) {
		return cb.call(this);
	}

	this._waiting.push({ test: test, cb: cb });
};

CondVariable.prototype.set = function (value) {
	this._value = value;

	for (var i = 0; i < this._waiting.length; i++) {
		var waiter = this._waiting[i];

		if (waiter.test(value)) {
			this._waiting.splice(i, 1);
			i -= 1;
			waiter.cb.call(this);
		}
	}
};

function Mutex() {
	this._isLocked = false;
	this._waiting = [];
}

Mutex.prototype.lock = function (cb) {
	if (this._isLocked) {
		this._waiting.push(cb);
	} else {
		this._isLocked = true;
		cb.call(this);
	}
};


Mutex.prototype.timedLock = function (ttl, cb) {
	if (!this._isLocked) {
		this._isLocked = true;
		return cb.call(this);
	}

	var timer, that = this;

	this._waiting.push(function () {
		clearTimeout(timer);

		if (!cb) {
			that.unlock();
			return;
		}

		cb.call(this);
		cb = null;
	});

	timer = setTimeout(function () {
		if (cb) {
			cb.call(this, new Error('Lock timed out'));
			cb = null;
		}
	}, ttl);
};

Object.defineProperty(Mutex.prototype, 'isLocked', {
	get: function () {
		return this._isLocked;
	}
});

Mutex.prototype.tryLock = function () {
	if (this._isLocked) {
		return false;
	}

	this._isLocked = true;
	return true;
};


Mutex.prototype.unlock = function () {
	if (!this._isLocked) {
		throw new Error('Mutex is not locked');
	}

	var waiter = this._waiting.shift();

	if (waiter) {
		waiter.call(this);
	} else {
		this._isLocked = false;
	}
};

Mutex.prototype.resetQueue = function() {
	this._waiting = [];
};

function ReadWriteLock() {
	this._isLocked = false;
	this._readLocks = 0;
	this._waitingToRead = [];
	this._waitingToWrite = [];
}

ReadWriteLock.prototype.readLock = function (cb) {
	if (this._isLocked === 'W') {
		this._waitingToRead.push(cb);
	} else {
		this._readLocks += 1;
		this._isLocked = 'R';
		cb.call(this);
	}
};


ReadWriteLock.prototype.writeLock = function (cb) {
	if (this._isLocked) {
		this._waitingToWrite.push(cb);
	} else {
		this._isLocked = 'W';
		cb.call(this);
	}
};


ReadWriteLock.prototype.timedReadLock = function (ttl, cb) {
	if (this.tryReadLock()) {
		return cb.call(this);
	}

	var timer, that = this;

	function waiter() {
		clearTimeout(timer);

		if (cb) {
			var callback = cb;
			cb = null;
			callback.apply(that, arguments);
		}
	}

	this._waitingToRead.push(waiter);

	timer = setTimeout(function () {
		var index = that._waitingToRead.indexOf(waiter);
		if (index !== -1) {
			that._waitingToRead.splice(index, 1);
			waiter(new Error('ReadLock timed out'));
		}
	}, ttl);
};


ReadWriteLock.prototype.timedWriteLock = function (ttl, cb) {
	if (this.tryWriteLock()) {
		return cb.call(this);
	}

	var timer, that = this;

	function waiter() {
		clearTimeout(timer);

		if (cb) {
			var callback = cb;
			cb = null;
			callback.apply(that, arguments);
		}
	}

	this._waitingToWrite.push(waiter);

	timer = setTimeout(function () {
		var index = that._waitingToWrite.indexOf(waiter);
		if (index !== -1) {
			that._waitingToWrite.splice(index, 1);
			waiter(new Error('WriteLock timed out'));
		}
	}, ttl);
};

Object.defineProperty(ReadWriteLock.prototype, 'isReadLocked', {
	get: function () {
		return this._isLocked === 'R';
	}
});

Object.defineProperty(ReadWriteLock.prototype, 'isWriteLocked', {
	get: function () {
		return this._isLocked === 'W';
	}
});

ReadWriteLock.prototype.tryReadLock = function () {
	if (this._isLocked === 'W') {
		return false;
	}

	this._isLocked = 'R';
	this._readLocks += 1;
	return true;
};


ReadWriteLock.prototype.tryWriteLock = function () {
	if (this._isLocked) {
		return false;
	}

	this._isLocked = 'W';
	return true;
};


ReadWriteLock.prototype.unlock = function () {
	var waiter;

	if (this._isLocked === 'R') {
		this._readLocks -= 1;

		if (this._readLocks === 0) {
			// allow one write lock through

			waiter = this._waitingToWrite.shift();
			if (waiter) {
				this._isLocked = 'W';
				waiter.call(this);
			} else {
				this._isLocked = false;
			}
		}
	} else if (this._isLocked === 'W') {
		// allow all read locks or one write lock through

		var rlen = this._waitingToRead.length;

		if (rlen === 0) {
			waiter = this._waitingToWrite.shift();
			if (waiter) {
				this._isLocked = 'W';
				waiter.call(this);
			} else {
				this._isLocked = false;
			}
		} else {
			this._isLocked = 'R';
			this._readLocks = rlen;

			var waiters = this._waitingToRead.slice();
			this._waitingToRead = [];

			for (var i = 0; i < rlen; i++) {
				waiters[i].call(this);
			}
		}
	} else {
		throw new Error('ReadWriteLock is not locked');
	}
};

function Semaphore(initialCount) {
	this._count = initialCount || 1;
	this._waiting = [];
}

Semaphore.prototype.wait = function (cb) {
	this._count -= 1;

	if (this._count < 0) {
		this._waiting.push(cb);
	} else {
		cb.call(this);
	}
};

Semaphore.prototype.signal = function () {
	this._count += 1;

	if (this._count <= 0) {
		var waiter = this._waiting.shift();
		if (waiter) {
			waiter.call(this);
		}
	}
};

const createCondVariable = function (initialValue) {
	return new CondVariable(initialValue);
};

const createSemaphore = function (initialCount) {
	return new Semaphore(initialCount);
};

const createMutex = function () {
	return new Mutex();
};

const createReadWriteLock = function () {
	return new ReadWriteLock();
};