Major restructuring and optimization
This commit is contained in:
1 parent
6e9bf5a027
commit
600ca58b99
5 files changed
+965
-1041
No files matched your search
+224
-228
@@ -19,31 +19,34 @@
|
||||
const common = require('./common');
|
||||
|
||||
|
||||
const CHANGE_FLAGS = {
|
||||
object: 4,
|
||||
writable: 16,
|
||||
value: 64
|
||||
};
|
||||
|
||||
|
||||
function sanitizeParameterValue(parameterValue) {
|
||||
if (parameterValue[0] != null) {
|
||||
switch (parameterValue[1]) {
|
||||
case 'xsd:boolean':
|
||||
if (typeof parameterValue[0] !== 'boolean') {
|
||||
parameterValue = parameterValue.slice();
|
||||
parameterValue[0] = !!JSON.parse(parameterValue[0]);
|
||||
}
|
||||
break;
|
||||
case 'xsd:string':
|
||||
if (typeof parameterValue[0] !== 'string') {
|
||||
parameterValue = parameterValue.slice();
|
||||
parameterValue[0] = '' + parameterValue[0];
|
||||
}
|
||||
break;
|
||||
case 'xsd:int':
|
||||
case 'xsd:unsignedInt':
|
||||
if (typeof parameterValue[0] !== 'number') {
|
||||
parameterValue = parameterValue.slice();
|
||||
parameterValue[0] = +parameterValue[0];
|
||||
}
|
||||
break;
|
||||
case 'xsd:dateTime':
|
||||
if (typeof parameterValue[0] !== 'number') {
|
||||
parameterValue = parameterValue.slice();
|
||||
if (parameterValue[0].getTime() != null)
|
||||
parameterValue[0] = parameterValue[0].getTime();
|
||||
else if (isNaN(parameterValue[0]))
|
||||
@@ -63,68 +66,6 @@ function sanitizeParameterValue(parameterValue) {
|
||||
}
|
||||
|
||||
|
||||
function inferTimestamps(deviceData, path) {
|
||||
common.addPathMeta(path);
|
||||
var res = {};
|
||||
|
||||
for (let sup of deviceData.paths.superset(path)) {
|
||||
for (let attrName in deviceData.timestamps) {
|
||||
let t = deviceData.timestamps[attrName].get(sup);
|
||||
if (t && !(t < res[sup]))
|
||||
res[attrName] = t;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = path.length - 1; i > 0; -- i) {
|
||||
if ((path.wildcard & ((1 << i) - 1)) == 0)
|
||||
break;
|
||||
|
||||
let param = null;
|
||||
let ancestor = path.slice(0, i);
|
||||
let ancestorTimestamp = 0;
|
||||
for (let sup of deviceData.paths.superset(ancestor)) {
|
||||
if (!sup.wildcard)
|
||||
param = sup;
|
||||
|
||||
let t = deviceData.timestamps.exist.get(sup);
|
||||
if (t > ancestorTimestamp)
|
||||
ancestorTimestamp = t;
|
||||
}
|
||||
|
||||
if (param != null && deviceData.values.exist.get(param)) {
|
||||
if (deviceData.values.object.get(param) == 0) {
|
||||
let t = deviceData.timestamps.object.get(param);
|
||||
for (let attrName in deviceData.timestamps)
|
||||
if (!(t <= res[attrName]))
|
||||
res[attrName] = t;
|
||||
}
|
||||
}
|
||||
else if (ancestorTimestamp) {
|
||||
let newt = {};
|
||||
for (let attrName in deviceData.timestamps)
|
||||
newt[attrName] = ancestorTimestamp;
|
||||
|
||||
for (let sub of deviceData.paths.subset(ancestor)) {
|
||||
if (sub.wildcard)
|
||||
continue;
|
||||
let subt = inferTimestamps(deviceData, sub.concat(path.slice(sub.length, path.length)));
|
||||
for (let attrName in newt) {
|
||||
if (!(attrName in subt))
|
||||
delete newt[attrName];
|
||||
else if (subt[attrName] < newt[attrName])
|
||||
newt[attrName] = subt[attrName];
|
||||
}
|
||||
}
|
||||
for (let attrName in newt)
|
||||
if (!(newt[attrName] <= res[attrName]))
|
||||
res[attrName] = newt[attrName];
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
function getAliasDeclarations(path, timestamp) {
|
||||
var decs = [];
|
||||
|
||||
@@ -141,9 +82,9 @@ function getAliasDeclarations(path, timestamp) {
|
||||
}
|
||||
|
||||
if (prefix.length)
|
||||
decs.push([pattern, {exist: timestamp, value: timestamp}]);
|
||||
decs.push([pattern, timestamp, {value: timestamp}]);
|
||||
else
|
||||
decs.push([pattern, {exist: timestamp}]);
|
||||
decs.push([pattern, timestamp]);
|
||||
}
|
||||
|
||||
recursive(path, []);
|
||||
@@ -154,50 +95,51 @@ function getAliasDeclarations(path, timestamp) {
|
||||
|
||||
function unpack(deviceData, path, revision) {
|
||||
let allMatches = [];
|
||||
if (path.alias == 0) {
|
||||
if (!path.alias) {
|
||||
for (let p of deviceData.paths.subset(path, 0))
|
||||
if (deviceData.values.exist.get(p, revision))
|
||||
if (deviceData.attributes.has(p, revision))
|
||||
allMatches.push(p);
|
||||
return allMatches;
|
||||
}
|
||||
else {
|
||||
let wildcardPath = path.slice();
|
||||
wildcardPath.wildcard = path.wildcard;
|
||||
for (let i = 0; i < wildcardPath.length; ++ i)
|
||||
if (Array.isArray(wildcardPath[i])) {
|
||||
wildcardPath[i] = '*';
|
||||
wildcardPath.wildcard |= 1 << i;
|
||||
}
|
||||
|
||||
let wildcardPath = path.slice();
|
||||
wildcardPath.wildcard = path.wildcard;
|
||||
for (let i = 0; i < wildcardPath.length; ++ i)
|
||||
if (Array.isArray(wildcardPath[i])) {
|
||||
wildcardPath[i] = '*';
|
||||
wildcardPath.wildcard |= 1 << i;
|
||||
}
|
||||
for (let p of deviceData.paths.subset(wildcardPath, 0))
|
||||
if (deviceData.attributes.has(p, revision))
|
||||
allMatches.push(p);
|
||||
|
||||
for (let p of deviceData.paths.subset(wildcardPath, 0))
|
||||
if (deviceData.values.exist.get(p, revision))
|
||||
allMatches.push(p);
|
||||
for (let i = path.length - 1; i >= 0; -- i) {
|
||||
if (Array.isArray(path[i])) {
|
||||
for (let j = 0; j < path[i].length; j += 2) {
|
||||
let p = wildcardPath.slice(0, i + 1).concat(path[i][j]);
|
||||
p.alias = (wildcardPath.alias & ((1 << (i + 1)) - 1)) + path[i][j].alias << (i + 1);
|
||||
p.wildcard = (wildcardPath.wildcard & ((1 << (i + 1)) - 1)) + path[i][j].wildcard << (i + 1);
|
||||
let unpacked = unpack(deviceData, p, revision);
|
||||
let filtered = [];
|
||||
for (let up of unpacked) {
|
||||
let attributes = deviceData.attributes.get(up, revision);
|
||||
if (attributes && attributes.value && attributes.value[1] &&
|
||||
sanitizeParameterValue([path[i][j + 1], attributes.value[1][1]])[0] === attributes.value[1][0]) {
|
||||
for (let m = 0; m < allMatches.length; ++ m) {
|
||||
let k;
|
||||
for (k = i; k >= 0; -- k)
|
||||
if (allMatches[m][k] != up[k])
|
||||
break
|
||||
|
||||
for (let i = path.length - 1; i >= 0; -- i) {
|
||||
if (Array.isArray(path[i])) {
|
||||
for (let j = 0; j < path[i].length; j += 2) {
|
||||
let p = wildcardPath.slice(0, i + 1).concat(path[i][j]);
|
||||
p.alias = (wildcardPath.alias & ((1 << (i + 1)) - 1)) + path[i][j].alias << (i + 1);
|
||||
p.wildcard = (wildcardPath.wildcard & ((1 << (i + 1)) - 1)) + path[i][j].wildcard << (i + 1);
|
||||
let unpacked = unpack(deviceData, p, revision);
|
||||
let filtered = [];
|
||||
for (let up of unpacked) {
|
||||
let v = deviceData.values.value.get(up, revision);
|
||||
if (v && sanitizeParameterValue([path[i][j + 1], v[1]])[0] == v[0]) {
|
||||
for (let m = 0; m < allMatches.length; ++ m) {
|
||||
let k;
|
||||
for (k = i; k >= 0; -- k)
|
||||
if (allMatches[m][k] != up[k])
|
||||
break
|
||||
|
||||
if (k < 0) {
|
||||
filtered.push(allMatches[m]);
|
||||
allMatches[m] = [];
|
||||
if (k < 0) {
|
||||
filtered.push(allMatches[m]);
|
||||
allMatches[m] = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
allMatches = filtered;
|
||||
}
|
||||
allMatches = filtered;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -209,7 +151,7 @@ function unpack(deviceData, path, revision) {
|
||||
|
||||
// Use numeric sorting for numbers
|
||||
let ia = parseInt(a), ib = parseInt(b);
|
||||
if (ia.toString() == a && ib.toString() == b) {
|
||||
if (ia.toString() === a && ib.toString() === b) {
|
||||
a = ia;
|
||||
b = ib;
|
||||
}
|
||||
@@ -226,167 +168,221 @@ function unpack(deviceData, path, revision) {
|
||||
}
|
||||
|
||||
|
||||
function normalizeAttributes(timestamps, values) {
|
||||
if ('value' in values && timestamps.value >= (timestamps.object || 0)) {
|
||||
values.object = 0;
|
||||
timestamps.object = timestamps.value;
|
||||
}
|
||||
function clear(deviceData, path, timestamp, attributes) {
|
||||
for (let p of deviceData.paths.all(path, timestamp ? 99 : 0)) {
|
||||
let t = deviceData.timestamps.get(p);
|
||||
if (!t)
|
||||
continue;
|
||||
|
||||
if (values.object && timestamps.object > (timestamps.value || 0)) {
|
||||
delete values.value;
|
||||
timestamps.value = timestamps.object;
|
||||
}
|
||||
let changeFlags = 0;
|
||||
|
||||
if ('value' in values && timestamps.value >= (timestamps.exist || 0)) {
|
||||
values.exist = 1;
|
||||
timestamps.exist = timestamps.value;
|
||||
}
|
||||
if (!(timestamp <= t)) {
|
||||
deviceData.timestamps.delete(p);
|
||||
deviceData.attributes.delete(p);
|
||||
changeFlags |= 3;
|
||||
} else if (t && attributes) {
|
||||
let currentAttributes = deviceData.attributes.get(p);
|
||||
if (currentAttributes) {
|
||||
let newAttrs;
|
||||
for (let attrName in attributes) {
|
||||
if (attrName in currentAttributes &&
|
||||
attributes[attrName][0] > currentAttributes[attrName][0]) {
|
||||
if (!newAttrs)
|
||||
newAttrs = JSON.parse(JSON.stringify(currentAttributes));
|
||||
delete newAttrs[attrName];
|
||||
changeFlags |= CHANGE_FLAGS[attrName];
|
||||
changeFlags |= CHANGE_FLAGS[attrName] << 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ('object' in values && timestamps.object >= (timestamps.exist || 0)) {
|
||||
values.exist = 1;
|
||||
timestamps.exist = timestamps.object;
|
||||
}
|
||||
if (newAttrs)
|
||||
deviceData.attributes.set(p, newAttrs);
|
||||
}
|
||||
}
|
||||
|
||||
if ('writable' in values && timestamps.writable >= (timestamps.exist || 0)) {
|
||||
values.exist = 1;
|
||||
timestamps.exist = timestamps.writable;
|
||||
if (changeFlags)
|
||||
for (let sup of deviceData.paths.superset(p)) {
|
||||
let c = deviceData.trackers.get(sup);
|
||||
for (let k in c)
|
||||
if (c[k] & changeFlags)
|
||||
deviceData.changes.add(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function clear(deviceData, path, timestamps, toClear) {
|
||||
if (!toClear)
|
||||
toClear = new Map();
|
||||
function compareEquality(a, b) {
|
||||
var t = typeof a;
|
||||
if (a === null ||
|
||||
a === undefined ||
|
||||
t === 'number' ||
|
||||
t === 'boolean' ||
|
||||
t === 'string' ||
|
||||
t === 'symbol')
|
||||
return a === b;
|
||||
|
||||
// Clear redundant subsets and deleted params
|
||||
for (let sub of deviceData.paths.subset(path)) {
|
||||
if (path.wildcard == sub.wildcard)
|
||||
continue;
|
||||
return JSON.stringify(a) === JSON.stringify(b);
|
||||
}
|
||||
|
||||
if (deviceData.values.exist.has(sub)) {
|
||||
let existTimestamp = deviceData.timestamps.exist.get(sub);
|
||||
for (let attrName in timestamps) {
|
||||
if (timestamps[attrName] > existTimestamp) {
|
||||
let inv = toClear.get(sub);
|
||||
if (inv == null)
|
||||
toClear.set(sub, inv = {});
|
||||
for (let attrName in deviceData.timestamps) {
|
||||
inv[attrName] = timestamps[attrName];
|
||||
deviceData.timestamps[attrName].delete(sub);
|
||||
deviceData.values[attrName].delete(sub);
|
||||
|
||||
function set(deviceData, path, timestamp, attributes, toClear) {
|
||||
path = deviceData.paths.add(path);
|
||||
|
||||
let changeFlags = 0;
|
||||
|
||||
let currentTimestamp = deviceData.timestamps.get(path);
|
||||
|
||||
let currentAttributes;
|
||||
|
||||
if (path.wildcard)
|
||||
attributes = undefined;
|
||||
else
|
||||
currentAttributes = deviceData.attributes.get(path);
|
||||
|
||||
if (attributes) {
|
||||
if (attributes.value && attributes.value[1]
|
||||
&& attributes.value[0] >= (attributes.object ? attributes.object[0] : 0))
|
||||
attributes.object = [attributes.value[0], 0];
|
||||
|
||||
if (attributes.object && attributes.object[1]
|
||||
&& attributes.object[0] >= (attributes.value ? attributes.value[0] : 0))
|
||||
attributes.value = [attributes.object[0]];
|
||||
|
||||
if (currentAttributes) {
|
||||
for (let attrName in attributes) {
|
||||
timestamp = Math.max(timestamp, attributes[attrName][0]);
|
||||
if (attrName in currentAttributes) {
|
||||
if (attributes[attrName][0] > currentAttributes[attrName][0]) {
|
||||
changeFlags |= CHANGE_FLAGS[attrName];
|
||||
if (compareEquality(attributes[attrName][1], currentAttributes[attrName][1]))
|
||||
changeFlags |= CHANGE_FLAGS[attrName] << 1;
|
||||
}
|
||||
break;
|
||||
else
|
||||
delete attributes[attrName];
|
||||
}
|
||||
else {
|
||||
changeFlags |= CHANGE_FLAGS[attrName];
|
||||
changeFlags |= CHANGE_FLAGS[attrName] << 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (timestamps[attrName] > deviceData.timestamps[attrName].get(sub)) {
|
||||
if (deviceData.values[attrName].has(sub)) {
|
||||
let inv = toClear.get(sub);
|
||||
if (inv == null)
|
||||
toClear.set(sub, inv = {});
|
||||
inv[attrName] = timestamps[attrName];
|
||||
deviceData.values[attrName].delete(sub);
|
||||
}
|
||||
deviceData.timestamps[attrName].set(sub, timestamps[attrName]);
|
||||
if (Object.keys(attributes).length) {
|
||||
if (attributes.object && attributes.object[1] === 0) {
|
||||
toClear = toClear || [];
|
||||
toClear.push([path.concat('*'), attributes.object[0]]);
|
||||
}
|
||||
for (let attrName in currentAttributes)
|
||||
if (!(attrName in attributes))
|
||||
attributes[attrName] = currentAttributes[attrName];
|
||||
|
||||
deviceData.attributes.set(path, attributes);
|
||||
}
|
||||
|
||||
if (timestamp > currentTimestamp) {
|
||||
deviceData.timestamps.set(path, timestamp);
|
||||
|
||||
if (path.length > 1)
|
||||
toClear = set(deviceData, path.slice(0, path.length - 1), timestamp,
|
||||
{object: [timestamp, 1]}, toClear);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (let attrName in timestamps) {
|
||||
if (attrName == 'exist') {
|
||||
for (let an in deviceData.timestamps)
|
||||
if (timestamps[attrName] > deviceData.timestamps[an].get(sub))
|
||||
deviceData.timestamps[an].delete(sub);
|
||||
continue;
|
||||
}
|
||||
else if (!(timestamp <= currentTimestamp)) {
|
||||
changeFlags |= 2;
|
||||
deviceData.timestamps.set(path, timestamp);
|
||||
deviceData.attributes.set(path, attributes);
|
||||
|
||||
if (timestamps[attrName] > deviceData.timestamps[attrName].get(sub))
|
||||
deviceData.timestamps[attrName].delete(sub);
|
||||
}
|
||||
if (path.length > 1)
|
||||
toClear = set(deviceData, path.slice(0, path.length - 1), timestamp,
|
||||
{object: [timestamp, 1]}, toClear);
|
||||
|
||||
toClear = toClear || [];
|
||||
toClear.push([path, timestamp]);
|
||||
}
|
||||
}
|
||||
else if (!(timestamp <= currentTimestamp)) {
|
||||
changeFlags |= 1;
|
||||
deviceData.timestamps.set(path, timestamp);
|
||||
|
||||
if (currentAttributes) {
|
||||
changeFlags |= 2;
|
||||
deviceData.attributes.delete(path);
|
||||
toClear = toClear || [];
|
||||
toClear.push([path, timestamp]);
|
||||
}
|
||||
|
||||
if (path.wildcard)
|
||||
for (let sub of deviceData.paths.subset(path))
|
||||
if (timestamp > deviceData.timestamps.get(sub)) {
|
||||
deviceData.timestamps.delete(sub);
|
||||
if (deviceData.attributes.delete(sub)) {
|
||||
toClear = toClear || [];
|
||||
toClear.push([sub, timestamp]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (changeFlags)
|
||||
for (let sup of deviceData.paths.superset(path)) {
|
||||
let c = deviceData.trackers.get(sup);
|
||||
for (let k in c)
|
||||
if (c[k] & changeFlags)
|
||||
deviceData.changes.add(k);
|
||||
}
|
||||
|
||||
return toClear;
|
||||
}
|
||||
|
||||
|
||||
function clearSupersets(deviceData, path, timestamps) {
|
||||
let depth = 0;
|
||||
if (timestamps.exist != null)
|
||||
depth = 99;
|
||||
function track(deviceData, path, marker, timestamp, attributeTimestamps, exist, attributeValues) {
|
||||
path = deviceData.paths.add(path);
|
||||
let f = 0;
|
||||
|
||||
for (let sup of deviceData.paths.superset(path, 99)) {
|
||||
if (sup.wildcard == path.wildcard && sup.length == path.length)
|
||||
continue;
|
||||
if (timestamp)
|
||||
f |= 1;
|
||||
|
||||
for (let attrName in deviceData.timestamps) {
|
||||
let t = deviceData.timestamps[attrName].get(sup);
|
||||
if (t <= timestamps[attrName] || t <= timestamps.exist) {
|
||||
deviceData.timestamps[attrName].delete(sup);
|
||||
deviceData.values[attrName].delete(sup);
|
||||
}
|
||||
}
|
||||
if (exist)
|
||||
f |= 2;
|
||||
|
||||
if (attributeTimestamps)
|
||||
for (let attrName in attributeTimestamps)
|
||||
f |= CHANGE_FLAGS[attrName];
|
||||
|
||||
if (attributeValues)
|
||||
for (let attrName in attributeValues)
|
||||
f |= CHANGE_FLAGS[attrName] << 1;
|
||||
|
||||
let cur = deviceData.trackers.get(path);
|
||||
if (!cur) {
|
||||
cur = {};
|
||||
deviceData.trackers.set(path, cur);
|
||||
}
|
||||
|
||||
cur[marker] |= f;
|
||||
}
|
||||
|
||||
|
||||
function set(deviceData, path, timestamps, values, toClear) {
|
||||
path = deviceData.paths.add(path);
|
||||
if (path.wildcard || values == null)
|
||||
values = {};
|
||||
|
||||
if (values.exist) {
|
||||
normalizeAttributes(timestamps, values);
|
||||
if (timestamps.exist && path.length > 1)
|
||||
toClear = set(deviceData, path.slice(0, path.length - 1),
|
||||
{exist: timestamps.exist, object: timestamps.exist},
|
||||
{exist: 1, object: 1},
|
||||
toClear);
|
||||
else if (!toClear)
|
||||
toClear = new Map();
|
||||
|
||||
// Clear attribute patterns
|
||||
for (let attrName in timestamps) {
|
||||
if (deviceData.values[attrName].has(path) != (attrName in values)) {
|
||||
let inv = toClear.get(path);
|
||||
if (inv == null)
|
||||
toClear.set(path, inv = {});
|
||||
inv[attrName] = timestamps[attrName];
|
||||
if (!(attrName in values))
|
||||
deviceData.values[attrName].delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
for (let attrName in timestamps) {
|
||||
deviceData.timestamps[attrName].set(path, timestamps[attrName]);
|
||||
if (attrName in values)
|
||||
deviceData.values[attrName].set(path, values[attrName]);
|
||||
}
|
||||
|
||||
return toClear;
|
||||
function clearTrackers(deviceData, tracker) {
|
||||
if (Array.isArray(tracker)) {
|
||||
deviceData.trackers.forEach(function(v, k) {
|
||||
for (let t of tracker)
|
||||
delete v[t];
|
||||
});
|
||||
for (let t of tracker)
|
||||
deviceData.changes.delete(t);
|
||||
}
|
||||
|
||||
// Clear redundant timestamps
|
||||
let current = inferTimestamps(deviceData, path);
|
||||
for (let c in timestamps)
|
||||
if (timestamps[c] <= current[c])
|
||||
delete timestamps[c];
|
||||
else if (c != 'exist' &&
|
||||
(timestamps[c] <= timestamps.exist || timestamps[c] <= current.exist))
|
||||
delete timetamps[c];
|
||||
|
||||
for (let attrName in timestamps) {
|
||||
deviceData.timestamps[attrName].set(path, timestamps[attrName]);
|
||||
if (attrName in values)
|
||||
deviceData.values[attrName].set(path, values[attrName]);
|
||||
else {
|
||||
deviceData.trackers.forEach(function(v, k) {
|
||||
delete v[tracker];
|
||||
});
|
||||
deviceData.changes.delete(tracker);
|
||||
}
|
||||
|
||||
return clear(deviceData, path, timestamps, toClear);
|
||||
}
|
||||
|
||||
|
||||
exports.sanitizeParameterValue = sanitizeParameterValue;
|
||||
exports.getAliasDeclarations = getAliasDeclarations;
|
||||
exports.inferTimestamps = inferTimestamps;
|
||||
exports.unpack = unpack;
|
||||
exports.set = set;
|
||||
exports.clear = clear;
|
||||
exports.clearSupersets = clearSupersets
|
||||
exports.track = track;
|
||||
exports.clearTrackers = clearTrackers;
|
||||
Reference in new issue
Block a user