diff --git a/lib/device.js b/lib/device.js index 05b21a1..f6dd46d 100644 --- a/lib/device.js +++ b/lib/device.js @@ -22,9 +22,9 @@ const common = require('./common'); const CHANGE_FLAGS = { - object: 4, - writable: 16, - value: 64 + object: 2, + writable: 4, + value: 8 }; @@ -63,6 +63,7 @@ function sanitizeParameterValue(parameterValue) { throw new Error(`Parameter value type "${parameterValue[1]}" not recognized.`); parameterValue[0] = JSON.parse(JSON.stringify(parameterValue[0])); + break; } } return parameterValue; @@ -171,36 +172,59 @@ function unpack(deviceData, path, revision) { } -function clear(deviceData, path, timestamp, attributes) { - for (let p of deviceData.paths.find(path, true, true, timestamp ? 99 : path.length)) { - for (let k in deviceData.trackers.get(p)) - deviceData.changes.add(k); +function clear(deviceData, path, timestamp, attributes, changeFlags = 0) { + let changeTrackers = {}; - let t = deviceData.timestamps.get(p); - if (!t) + timestamp = timestamp || 0; + + let descendantsTimestamp = timestamp; + if (attributes && attributes.object) { + if (attributes.object > descendantsTimestamp) + descendantsTimestamp = attributes.object; + if (!(attributes.object <= attributes.value)) + attributes.value = attributes.object; + } + + for (let p of deviceData.paths.find(path, true, true, descendantsTimestamp ? 99 : path.length)) { + let tracker = deviceData.trackers.get(p); + for (let k in tracker) + changeTrackers[k] |= tracker[k]; + + let currentTimestamp = deviceData.timestamps.get(p); + if (currentTimestamp === undefined) continue; - if (!(timestamp <= t)) { + if (timestamp > currentTimestamp || + (descendantsTimestamp > currentTimestamp && p.length > path.length)) { deviceData.timestamps.delete(p); deviceData.attributes.delete(p); - } else if (t && attributes) { + changeFlags |= 1; + } + else if (attributes && p.length == path.length) { 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)); + attributes[attrName] > currentAttributes[attrName][0]) { + changeFlags |= CHANGE_FLAGS[attrName]; + if (!newAttrs) { + newAttrs = Object.assign({}, currentAttributes); + deviceData.attributes.set(p, newAttrs); + } delete newAttrs[attrName]; } } - - if (newAttrs) - deviceData.attributes.set(p, newAttrs); } } } + + // Note: For performance, we're merging all changes together rather than + // mark changes based the exact parameters affected. + for (let k in changeTrackers) { + if (changeTrackers[k] & changeFlags) + deviceData.changes.add(k); + } } @@ -227,9 +251,11 @@ function set(deviceData, path, timestamp, attributes, toClear) { if (path.wildcard) attributes = undefined; - else + else if (currentTimestamp) currentAttributes = deviceData.attributes.get(path); + let changeFlags = 0; + if (attributes) { if (attributes.value && attributes.value[1] && attributes.value[0] >= (attributes.object ? attributes.object[0] : 0)) @@ -239,67 +265,29 @@ function set(deviceData, path, timestamp, attributes, toClear) { && attributes.object[0] >= (attributes.value ? attributes.value[0] : 0)) attributes.value = [attributes.object[0]]; - if (currentAttributes) { - let changeFlags = 0; + let newAttributes = Object.assign({}, currentAttributes, attributes); + 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; - } - else - delete attributes[attrName]; - } - else { + if (!(attrName in currentAttributes)) + changeFlags |= CHANGE_FLAGS[attrName]; + else if (attributes[attrName][0] <= currentAttributes[attrName][0]) + newAttributes[attrName] = currentAttributes[attrName]; + else if (!compareEquality(attributes[attrName][1], currentAttributes[attrName][1])) changeFlags |= CHANGE_FLAGS[attrName]; - changeFlags |= CHANGE_FLAGS[attrName] << 1; - } } - - if (Object.keys(attributes).length) { - if (attributes.object && currentAttributes.object && - attributes.object[1] !== currentAttributes.object[1]) { - 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) { - changeFlags |= 1; - deviceData.timestamps.set(path, timestamp); - - if (path.length > 1) - toClear = set(deviceData, path.slice(0, path.length - 1), timestamp, - {object: [timestamp, 1]}, toClear); - } - - if (changeFlags) - for (let sup of deviceData.paths.find(path, true, false)) { - let c = deviceData.trackers.get(sup); - for (let k in c) - if (c[k] & changeFlags) { - deviceData.changes.add(k); - } - } } - else if (!(timestamp <= currentTimestamp)) { - deviceData.timestamps.set(path, timestamp); - deviceData.attributes.set(path, attributes); + else + changeFlags |= 1; + deviceData.attributes.set(path, newAttributes); + + 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); - - toClear = toClear || []; - toClear.push([path, timestamp]); } } else if (!(timestamp <= currentTimestamp)) { @@ -307,43 +295,49 @@ function set(deviceData, path, timestamp, attributes, toClear) { if (currentAttributes) { deviceData.attributes.delete(path); - toClear = toClear || []; - toClear.push([path, timestamp]); + changeFlags |= 1; } - - if (path.wildcard) - for (let sub of deviceData.paths.find(path, false, true)) - if (timestamp > deviceData.timestamps.get(sub)) { - deviceData.timestamps.delete(sub); - if (deviceData.attributes.delete(sub)) { - toClear = toClear || []; - toClear.push([sub, timestamp]); - } + else if (path.wildcard) { + for (let p of deviceData.paths.find(path, false, true, path.length)) + if (timestamp > deviceData.timestamps.get(p)) { + toClear = toClear || []; + toClear.push([p, timestamp]); } + } + } + + if (changeFlags) { + if (changeFlags & 1) { + toClear = toClear || []; + toClear.push([path, timestamp, null, changeFlags]); + } + else if (changeFlags & CHANGE_FLAGS.object) { + toClear = toClear || []; + toClear.push([path, 0, {object: attributes.object[0]}, changeFlags]); + } + else { + for (let p of deviceData.paths.find(path, true, false, path.length)) { + let tracker = deviceData.trackers.get(p); + for (let k in tracker) { + if (tracker[k] & changeFlags) + deviceData.changes.add(k); + } + } + } } return toClear; } -function track(deviceData, path, marker, timestamp, attributeTimestamps, exist, attributeValues) { +function track(deviceData, path, marker, attributes) { path = deviceData.paths.add(path); - let f = 0; + let f = 1; - if (timestamp) - f |= 1; - - if (exist) - f |= 2; - - if (attributeTimestamps) - for (let attrName of attributeTimestamps) + if (attributes) + for (let attrName of attributes) f |= CHANGE_FLAGS[attrName]; - if (attributeValues) - for (let attrName of attributeValues) - f |= CHANGE_FLAGS[attrName] << 1; - let cur = deviceData.trackers.get(path); if (!cur) { cur = {}; diff --git a/lib/session.coffee b/lib/session.coffee index 4336a61..94f33cf 100644 --- a/lib/session.coffee +++ b/lib/session.coffee @@ -506,7 +506,7 @@ runDeclarations = (sessionContext, declarations) -> attrTrackers.push(k) cur[k] = Math.max(v, cur[k] or 0) - device.track(sessionContext.deviceData, p, 'prerequisite', false, null, true, attrTrackers) + device.track(sessionContext.deviceData, p, 'prerequisite', attrTrackers) unpacked = device.unpack(sessionContext.deviceData, path) for u in unpacked @@ -538,7 +538,7 @@ runDeclarations = (sessionContext, declarations) -> for k, v of declaration[4] cur[k] = v - device.track(sessionContext.deviceData, path, 'prerequisite', false, null, true) + device.track(sessionContext.deviceData, path, 'prerequisite') if declaration[3]? if Array.isArray(declaration[3]) @@ -603,6 +603,14 @@ rpcRequest = (sessionContext, _declarations, callback) -> fault.timestamp = sessionContext.timestamp return callback(null, fault) + # Enforce max clear timestamp + for c in toClear + if c[1] > sessionContext.timestamp + c[1] = sessionContext.timestamp + for k, v of c[2] + if v > sessionContext.timestamp + c[2] = sessionContext.timestamp + sessionContext.declarations.push(decs) sessionContext.provisionsRet[inception] = ret @@ -1307,14 +1315,20 @@ clear = (sessionContext, toClear, callback) -> MAX_DEPTH = config.get('MAX_DEPTH', sessionContext.deviceId) toClear.forEach((c) -> - loadPath(sessionContext, c[0], ((1 << c[0].length) - 1) ^ ((1 << MAX_DEPTH) - 1)) + if c[1] + p = c[0].slice(0, -1) # in order to include superset + loadPath(sessionContext, p, ((1 << p.length) - 1) ^ ((1 << MAX_DEPTH) - 1)) + else if (c[2] and c[2].object) + loadPath(sessionContext, c[0], (((1 << c[0].length) - 1) >> 1) ^ ((1 << MAX_DEPTH) - 1)) + else + loadPath(sessionContext, c[0], (1 << c[0].length) >> 1) ) loadParameters(sessionContext, (err) -> return callback(err) if err toClear.forEach((c) -> - device.clear(sessionContext.deviceData, c[0], c[1], c[2]) + device.clear(sessionContext.deviceData, c[0], c[1], c[2], c[3]) ) return callback() )