public static IEnumerable <Warning> Warnings(FlowGraph graph, IEnumerable <IUnitPortDefinition> definitions = null) { if (definitions == null) { definitions = LinqUtility.Concat <IUnitPortDefinition>(graph.controlInputDefinitions, graph.controlOutputDefinitions, graph.valueInputDefinitions, graph.valueOutputDefinitions); } var hasDuplicate = definitions.DistinctBy(d => d.key).Count() != definitions.Count(); if (hasDuplicate) { yield return(Warning.Caution("Some port definitions with non-unique keys are currently ignored.")); } foreach (var definition in definitions) { if (!definition.isValid) { yield return(InvalidWarning(definition)); } } }
protected override IEnumerable <Warning> Warnings() { foreach (var baseWarning in base.Warnings()) { yield return(baseWarning); } var graph = transition.nest.graph; if (graph == null) { yield break; } using (var recursion = Recursion.New(1)) { if (!graph.GetUnitsRecursive(recursion).OfType <IEventUnit>().Any()) { yield return(Warning.Caution("Transition graph is missing an event.")); } } using (var recursion = Recursion.New(1)) { if (!graph.GetUnitsRecursive(recursion).OfType <TriggerStateTransition>().Any()) { yield return(Warning.Caution("Transition graph is missing a trigger unit.")); } } }
protected override IEnumerable <Warning> Warnings() { foreach (var baseWarning in base.Warnings()) { yield return(baseWarning); } if (target.member != null && target.member.isReflected) { var obsoleteAttribute = target.member.info.GetAttribute <ObsoleteAttribute>(); if (obsoleteAttribute != null) { if (obsoleteAttribute.Message != null) { Debug.LogWarning($"\"{target.member.name}\" unit member is deprecated: {obsoleteAttribute.Message}"); yield return(Warning.Caution("Deprecated: " + obsoleteAttribute.Message)); } else { Debug.LogWarning($"\"{target.member.name}\" unit member is deprecated."); yield return(Warning.Caution($"Member {target.member.name} is deprecated.")); } } } }
public static Warning InvalidWarning(IUnitPortDefinition definition) { if (!StringUtility.IsNullOrWhiteSpace(definition.label)) { return(Warning.Caution($"{definition.GetType().HumanName().ToLower().FirstCharacterToUpper()} '{definition.label}' is not properly configured and is currently ignored.")); } else { return(Warning.Caution($"A {definition.GetType().HumanName().ToLower()} with incomplete configuration is currently ignored.")); } }
protected override IEnumerable <Warning> Warnings() { foreach (var baseWarning in base.Warnings()) { yield return(baseWarning); } if (transition.childGraph == null) { yield return(Warning.Caution("Missing transition graph.")); } if (transition.nest.hasBackgroundEmbed) { yield return(Warning.Caution("Background embed graph detected.")); } }
protected override IEnumerable <Warning> Warnings() { foreach (var baseWarning in base.Warnings()) { yield return(baseWarning); } if (unit.graph != null) { foreach (var definitionWarning in UnitPortDefinitionUtility.Warnings(unit.graph, LinqUtility.Concat <IUnitPortDefinition>(unit.graph.controlOutputDefinitions, unit.graph.valueOutputDefinitions))) { yield return(definitionWarning); } if (unit.graph.units.Count(unit => unit is GraphOutput) > 1) { yield return(Warning.Caution("Multiple output units in the same graph. Only one of them will be used.")); } } }
protected virtual IEnumerable <Warning> Warnings() { var isEntered = IsEntered(); if (!unit.isDefined) { if (unit.definitionException != null) { yield return(Warning.Exception(unit.definitionException)); } else if (!unit.canDefine) { yield return(Warning.Caution("Unit is not properly configured.")); } } if (!isEntered) { yield return(Warning.Info("Unit is never entered.")); } // Obsolete attribute is not inherited, so traverse the chain manually var obsoleteAttribute = unit.GetType().AndHierarchy().FirstOrDefault(t => t.HasAttribute <ObsoleteAttribute>())?.GetAttribute <ObsoleteAttribute>(); if (obsoleteAttribute != null) { var unitName = BoltFlowNameUtility.UnitTitle(unit.GetType(), true, false); if (obsoleteAttribute.Message != null) { Debug.LogWarning($"\"{unitName}\" unit is deprecated: {obsoleteAttribute.Message}"); yield return(Warning.Caution($"Deprecated: {obsoleteAttribute.Message}")); } else { Debug.LogWarning($"\"{unitName}\" unit is deprecated."); yield return(Warning.Caution("This unit is deprecated.")); } } if (unit.isDefined) { foreach (var invalidInput in unit.invalidInputs) { yield return(Warning.Caution($"{PortLabel(invalidInput)} is not used by this unit.")); } foreach (var invalidOutput in unit.invalidOutputs) { yield return(Warning.Caution($"{PortLabel(invalidOutput)} is not provided by this unit.")); } foreach (var validPort in unit.validPorts) { if (validPort.hasInvalidConnection) { yield return(Warning.Caution($"{PortLabel(validPort)} has an invalid connection.")); } } } foreach (var controlInput in unit.controlInputs) { if (!controlInput.hasValidConnection) { continue; } foreach (var relation in controlInput.relations) { if (relation.source is ValueInput) { var valueInput = (ValueInput)relation.source; foreach (var warning in ValueInputWarnings(valueInput)) { yield return(warning); } } } } foreach (var controlOutput in unit.controlOutputs) { if (!controlOutput.hasValidConnection) { continue; } var controlInputs = controlOutput.relations.Select(r => r.source).OfType <ControlInput>(); var isTriggered = !controlInputs.Any() || controlInputs.Any(ci => !ci.isPredictable || ci.couldBeEntered); foreach (var relation in controlOutput.relations) { if (relation.source is ValueInput) { var valueInput = (ValueInput)relation.source; foreach (var warning in ValueInputWarnings(valueInput)) { yield return(warning); } } } if (isEntered && !isTriggered) { yield return(Warning.Caution($"{PortLabel(controlOutput)} is connected, but it is never triggered.")); } } foreach (var valueOutput in unit.valueOutputs) { if (!valueOutput.hasValidConnection) { continue; } foreach (var relation in valueOutput.relations) { if (relation.source is ControlInput) { var controlInput = (ControlInput)relation.source; if (isEntered && controlInput.isPredictable && !controlInput.couldBeEntered) { yield return(Warning.Severe($"{PortLabel(controlInput)} is required, but it is never entered.")); } } else if (relation.source is ValueInput) { var valueInput = (ValueInput)relation.source; foreach (var warning in ValueInputWarnings(valueInput)) { yield return(warning); } } } } }
private IEnumerable <Warning> ValueInputWarnings(ValueInput valueInput) { // We can disable null reference check if no self is available // and the port requires an owner, for example in macros. var trustFutureOwner = valueInput.nullMeansSelf && reference.self == null; var checkForNullReference = BoltFlow.Configuration.predictPotentialNullReferences && !valueInput.allowsNull && !trustFutureOwner; var checkForMissingComponent = BoltFlow.Configuration.predictPotentialMissingComponents && typeof(Component).IsAssignableFrom(valueInput.type); // Note that we cannot directly check the input's predicted value, because it // will return false for safeguard specifically because it might be missing requirements. // Therefore, we first check the connected value, then the default value. // If the port is connected to a predictable output, use the connected value to perform checks. if (valueInput.hasValidConnection) { var valueOutput = valueInput.validConnectedPorts.Single(); if (Flow.CanPredict(valueOutput, reference)) { if (checkForNullReference) { if (Flow.Predict(valueOutput, reference) == null) { yield return(Warning.Severe($"{PortLabel(valueInput)} cannot be null.")); } } if (checkForMissingComponent) { var connectedPredictedValue = Flow.Predict(valueOutput, reference); // This check is necessary, because the predicted value could be // incompatible as connections with non-guaranteed conversions are allowed. if (ConversionUtility.CanConvert(connectedPredictedValue, typeof(GameObject), true)) { var gameObject = ConversionUtility.Convert <GameObject>(connectedPredictedValue); if (gameObject != null) { var component = (Component)ConversionUtility.Convert(gameObject, valueInput.type); if (component == null) { yield return(Warning.Caution($"{PortLabel(valueInput)} is missing a {valueInput.type.DisplayName()} component.")); } } } } } } // If the port isn't connected but has a default value, use the default value to perform checks. else if (valueInput.hasDefaultValue) { if (checkForNullReference) { if (Flow.Predict(valueInput, reference) == null) { yield return(Warning.Severe($"{PortLabel(valueInput)} cannot be null.")); } } if (checkForMissingComponent) { var unconnectedPredictedValue = Flow.Predict(valueInput, reference); if (ConversionUtility.CanConvert(unconnectedPredictedValue, typeof(GameObject), true)) { var gameObject = ConversionUtility.Convert <GameObject>(unconnectedPredictedValue); if (gameObject != null) { var component = (Component)ConversionUtility.Convert(gameObject, valueInput.type); if (component == null) { yield return(Warning.Caution($"{PortLabel(valueInput)} is missing a {valueInput.type.DisplayName()} component.")); } } } } } // The value isn't connected and has no default value, // therefore it is certain to be missing at runtime. else { yield return(Warning.Severe($"{PortLabel(valueInput)} is missing.")); } }