Skip to content

Instantly share code, notes, and snippets.

@oxysoft
Created September 13, 2020 18:52
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oxysoft/b790dba3125b3ae36535f52e4469e65d to your computer and use it in GitHub Desktop.
Save oxysoft/b790dba3125b3ae36535f52e4469e65d to your computer and use it in GitHub Desktop.
#if UNITY_EDITOR
using System;
using System.Linq;
using OdinExtensions;
using Sirenix.OdinInspector.Editor;
using Sirenix.OdinInspector.Editor.Validation;
using Sirenix.Serialization;
using UnityEngine;
using Object = UnityEngine.Object;
[assembly: RegisterValidator(typeof(AllRequiredValidator), Priority = 500)]
namespace OdinExtensions
{
/// <summary>
/// Makes all fields that take an object to be REQUIRED by default.
/// An 'OptionalAttribute' can be used to make objects optional.
///
/// Built-in unity types and other plugins things should not be included,
/// as they can often introduce random errors since they were not designed
/// with this in mind.
///
/// Use the whitelist to select namespaces to include.
/// You can also use the blacklist to block subsections
/// of the whitelist.
/// </summary>
public class AllRequiredValidator : Validator
{
public override RevalidationCriteria RevalidationCriteria => RevalidationCriteria.OnValueChange;
// CUSTOMIZABLE WHITELIST (namespaces)
// ----------------------------------------
private static readonly string[] Whitelist =
{
// Fill me up
"Demo"
};
// CUSTOMIZABLE BLACKLIST (namespaces)
// ----------------------------------------
private static readonly string[] Blacklist =
{
// Fill me up
};
private static bool IsTypeSupported(Type type)
{
string typeNamespace = type.Namespace;
if (typeNamespace == null)
return false;
return Whitelist.Any(w => typeNamespace.StartsWith(w)) &&
!Blacklist.Any(b => typeNamespace.StartsWith(b));
}
private static bool IsValid(IPropertyValueEntry valueEntry)
{
object v = valueEntry?.WeakSmartValue;
if (v == null) return false;
if (v is Object o && o == null) return false;
if (v is string s && string.IsNullOrEmpty(s)) return false;
return true;
}
public override void RunValidation(ref ValidationResult result)
{
if (Property.ValueEntry == null) return;
if (Property.Name.StartsWith("$")) return;
if (result == null)
{
result = new ValidationResult
{
Setup = new ValidationSetup
{
Root = Property.SerializationRoot.ValueEntry.WeakValues[0] as Object,
Member = Property.Info.GetMemberInfo(),
Validator = this
},
ResultType = ValidationResultType.Valid
};
}
// Check to see that this property is allowed for AllRequired
Type parentType = Property.ParentType;
if (!IsTypeSupported(parentType))
return;
// Apply the result
if (IsValid(Property.ValueEntry))
{
result.ResultType = ValidationResultType.Valid;
result.Message = null;
}
else
{
if (Property.GetAttribute<OptionalAttribute>() != null)
return;
result.ResultType = ValidationResultType.Error;
result.Message = $"'{Property.Name}' must be assigned. All public values are serialized in Unity. Values which are not meant for configuration must be marked with NonSerialized.";
}
}
public override bool CanValidateProperty(InspectorProperty property)
{
bool is_public_field = property.Info.IsEditable && property.Info.HasBackingMembers;
bool serialized_attr = property.GetAttribute<SerializeField>() != null;
bool serialized_attr_odin = property.GetAttribute<OdinSerializeAttribute>() != null;
return is_public_field || serialized_attr || serialized_attr_odin;
}
}
}
#endif
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class OptionalAttribute : Attribute
{ }
@oxysoft
Copy link
Author

oxysoft commented Sep 13, 2020

AllRequiredValidator

An Odin Validator which will make everything [Required] by default. A new [Optional] attribute is added instead to flip the logic.

Usage

  1. Import Odin Inspector.
  2. Add this script to your project's Editor folder or editor assembly.
  3. Edit the whitelist and blacklist to include and exclude namespaces. Nothing is included by default.
  4. All properties for components and scriptable objects matching those namespaces will be included. Strings must also be filled by default.
  5. Use the optional attribute to mark things as optional.

image

image

These screenshots use the MiniValidationDrawers

@funnymanwin
Copy link

Does not work with new version of Odin

@OlivierLapointe
Copy link

OlivierLapointe commented Dec 12, 2022

@funnymanwin got an updated version somewhat working:
Odin version: 3.1.8 (~ 2022/12/01)

using UnityEngine;
using Sirenix.OdinInspector.Editor;
using Sirenix.OdinInspector;
using System.Collections.Generic;
using System;
using System.Reflection;
using Sirenix.Serialization;

/// <summary>
/// Makes every nullable property field show an Error warning in inspector if null.
/// Can be overriden by using <see cref="OptionalAttribute"/>
/// </summary>
/// <typeparam name="T"></typeparam>
public class DefaultRequiredAttributeProcessor<T> : OdinAttributeProcessor<T> 
{
    public override void ProcessChildMemberAttributes(
        InspectorProperty parentProperty,
        MemberInfo member,
        List<Attribute> attributes)
    {
        // These attributes will be added to all of the child elements.

        if (member.GetCustomAttribute<OptionalAttribute>() == null &&
            member.GetCustomAttribute<NonSerializedAttribute>() == null &&

            (member.GetCustomAttribute<SerializeField>() != null ||
            member.GetCustomAttribute<OdinSerializeAttribute>() != null)
            )
        {
            attributes.Add(new RequiredAttribute("Missing value", InfoMessageType.Error));
        }

    }
}

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
/// OURS ! USE THIS ONE.
public class OptionalAttribute : Attribute
{ }

@MrDizzle
Copy link

MrDizzle commented Jan 2, 2023

@funnymanwin got an updated version somewhat working: Odin version: 3.1.8 (~ 2022/12/01)

using UnityEngine;
using Sirenix.OdinInspector.Editor;
using Sirenix.OdinInspector;
using System.Collections.Generic;
using System;
using System.Reflection;
using Sirenix.Serialization;

/// <summary>
/// Makes every nullable property field show an Error warning in inspector if null.
/// Can be overriden by using <see cref="OptionalAttribute"/>
/// </summary>
/// <typeparam name="T"></typeparam>
public class DefaultRequiredAttributeProcessor<T> : OdinAttributeProcessor<T> 
{
    public override void ProcessChildMemberAttributes(
        InspectorProperty parentProperty,
        MemberInfo member,
        List<Attribute> attributes)
    {
        // These attributes will be added to all of the child elements.

        if (member.GetCustomAttribute<OptionalAttribute>() == null &&
            member.GetCustomAttribute<NonSerializedAttribute>() == null &&

            (member.GetCustomAttribute<SerializeField>() != null ||
            member.GetCustomAttribute<OdinSerializeAttribute>() != null)
            )
        {
            attributes.Add(new RequiredAttribute("Missing value", InfoMessageType.Error));
        }

    }
}

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
/// OURS ! USE THIS ONE.
public class OptionalAttribute : Attribute
{ }

What does "somewhat" mean in this instance? XD

@funnymanwin
Copy link

I understood why it doesn't work with new version of Odin inspector!
After one of updates Odin Inspector split up to Odin inspector and Odin validator. All validation functionality was removed from Odin Inspector. And now if you want to have validation addons like this you must pay another $45! Totally Odin Inspector and Validator cost almost $100. Now it became so expensive...

@funnymanwin
Copy link

funnymanwin commented Oct 13, 2023

I updated your code. Compatible with new versions of Odin. Anyway u will not read this, because you abandoned this script long time ago. So I hope somebody will say thanks to me (i spent a lot of hours trying to fix it):

#if UNITY_EDITOR
using System;
using System.Linq;
using OdinExtensions;
using Sirenix.OdinInspector.Editor;
using Sirenix.OdinInspector.Editor.Validation;
using Sirenix.Serialization;
using UnityEngine;
using UnityEngine.AddressableAssets;
using Object = UnityEngine.Object;

[assembly: RegisterValidator(typeof(AllRequiredValidator), Priority = 500)]

namespace OdinExtensions
{
	/// <summary>
	/// Makes all fields that take an object to be REQUIRED by default.
	/// An 'OptionalAttribute' can be used to make objects optional.
	///
	/// Built-in unity types and other plugins things should not be included,
	/// as they can often introduce random errors since they were not designed
	/// with this in mind.
	///
	/// Use the whitelist to select namespaces to include.
	/// You can also use the blacklist to block subsections
	/// of the whitelist.
	/// </summary>
	public class AllRequiredValidator : Validator
	{
		public override RevalidationCriteria RevalidationCriteria => RevalidationCriteria.OnValueChange;
		

		private static bool IsValid(IPropertyValueEntry valueEntry)
		{
			object v = valueEntry?.WeakSmartValue;
			
			if (v == null) return false;
			if (v is Object o && o == null) return false;
			if (v is string s && string.IsNullOrEmpty(s)) return false;
			if (v is AssetReference assetReference && !assetReference.RuntimeKeyIsValid()) return false;

			return true;
		}

		public override void RunValidation(ref ValidationResult result)
		{
			if (Property.ValueEntry == null) return;
			if (Property.Name.StartsWith("$")) return;

			if (result == null)
			{
				result = new ValidationResult
				{
					Setup = new ValidationSetup
					{
						Root      = Property.SerializationRoot.ValueEntry.WeakValues[0] as Object,
						ParentInstance = Property.Parent,
						//Member    = Property.Info.GetMemberInfo(),
						Validator = this
					},
					ResultType = ValidationResultType.Valid
				};
			}


			// Apply the result
			if (IsValid(Property.ValueEntry))
			{
				result.ResultType = ValidationResultType.Valid;
				result.Message    = null;
			}
			else
			{
				if (Property.GetAttribute<OptionalAttribute>() != null)
					return;

				result.ResultType = ValidationResultType.Error;
				result.Message    = $"'{Property.Name}' must be assigned. All public values are serialized in Unity. Values which are not meant for configuration must be marked with NonSerialized.";
			}
		}

		public override bool CanValidateProperty(InspectorProperty property)
		{
			bool is_public_field      = property.Info.IsEditable && property.Info.HasBackingMembers;
			bool serialized_attr      = property.GetAttribute<SerializeField>() != null;
			bool serialized_attr_odin = property.GetAttribute<OdinSerializeAttribute>() != null;

			return is_public_field || serialized_attr || serialized_attr_odin;
		}
	}
}
#endif

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class OptionalAttribute : Attribute
{ }

image
image

Or you can just download final version as Unity Package with MiniValidationDrawer.cs from my Google Drive:
https://drive.google.com/file/d/1i1Kmzrd8fikuAZqnFYe4-jsAnwO_YnHO/view?usp=sharing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment