]> git.sesse.net Git - casparcg/blobdiff - core/producer/binding.h
[scene] Fixed double evaluation of expressions
[casparcg] / core / producer / binding.h
index 1c13f11a0f4c589272e569022d1413abb7567b70..17c9cff199a40a306d3125b24d6bbd67d44acab7 100644 (file)
 #include <functional>
 #include <memory>
 #include <vector>
+#include <string>
 #include <map>
 #include <algorithm>
+#include <type_traits>
+#include <stdexcept>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/utility/value_init.hpp>
+
+#include <common/tweener.h>
+#include <common/except.h>
 
 namespace caspar { namespace core {
 
+namespace detail {
+
+struct impl_base : std::enable_shared_from_this<impl_base>
+{
+       std::vector<std::shared_ptr<impl_base>> dependencies_;
+       mutable std::vector<std::pair<
+                       std::weak_ptr<void>,
+                       std::function<void ()>>> on_change_;
+
+       virtual ~impl_base()
+       {
+       }
+
+       virtual void evaluate() const = 0;
+
+       void depend_on(const std::shared_ptr<impl_base>& dependency)
+       {
+               auto self = shared_from_this();
+
+               if (dependency->depends_on(self))
+                       CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info("Can't have circular dependencies between bindings"));
+
+               dependency->on_change(self, [=] { evaluate(); });
+               dependencies_.push_back(dependency);
+       }
+
+       bool depends_on(const std::shared_ptr<impl_base>& other) const
+       {
+               for (auto& dependency : dependencies_)
+               {
+                       if (dependency == other)
+                               return true;
+
+                       if (dependency->depends_on(other))
+                               return true;
+               }
+
+               return false;
+       }
+
+       void on_change(
+                       const std::weak_ptr<void>& dependant,
+                       const std::function<void ()>& listener) const
+       {
+               on_change_.push_back(std::make_pair(dependant, listener));
+       }
+};
+
+}
+
 template <typename T>
 class binding
 {
 private:
-       struct impl : std::enable_shared_from_this<impl>
+
+       struct impl : public detail::impl_base
        {
-               T value_;
+               mutable T                       value_;
                std::function<T ()> expression_;
-               std::vector<std::shared_ptr<impl>> dependencies_;
-               mutable std::vector<std::pair<
-                               std::weak_ptr<void>,
-                               std::function<void ()>>> on_change_;
+               mutable bool            evaluated_              = false;
 
                impl()
+                       : value_(boost::value_initialized<T>())
                {
                }
 
@@ -51,13 +109,18 @@ private:
                {
                }
 
-               impl(const std::function<T ()>& expression)
-                       : expression_(expression)
+               template<typename Expr>
+               impl(const Expr& expression)
+                       : value_(boost::value_initialized<T>())
+                       , expression_(expression)
                {
                }
 
                T get() const
                {
+                       if (!evaluated_)
+                               evaluate();
+
                        return value_;
                }
 
@@ -69,9 +132,7 @@ private:
                void set(T value)
                {
                        if (bound())
-                       {
-                               throw std::exception("Bound value cannot be set");
-                       }
+                               CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info("Bound value cannot be set"));
 
                        if (value == value_)
                                return;
@@ -81,34 +142,26 @@ private:
                        on_change();
                }
 
-               void depend_on(const std::shared_ptr<impl>& dependency)
-               {
-                       dependency->on_change(shared_from_this(), [=] { evaluate(); });
-                       dependencies_.push_back(dependency);
-               }
-
-               void on_change(
-                               const std::weak_ptr<void>& dependant,
-                               const std::function<void ()>& listener) const
-               {
-                       on_change_.push_back(std::make_pair(dependant, listener));
-               }
-
-               void evaluate()
+               void evaluate() const override
                {
-                       if (expression_)
+                       if (bound())
                        {
                                auto new_value = expression_();
 
+                               evaluated_ = true;
+
                                if (new_value != value_)
                                {
                                        value_ = new_value;
                                        on_change();
                                }
                        }
+                       else
+                               evaluated_ = true;
                }
 
-               void on_change()
+               using impl_base::on_change;
+               void on_change() const
                {
                        auto copy = on_change_;
 
@@ -127,13 +180,13 @@ private:
                {
                        unbind();
                        depend_on(other);
-                       expression_ = [&]{ return other.get(); }
+                       expression_ = [other]{ return other->get(); };
                        evaluate();
                }
 
                void unbind()
                {
-                       if (expression_)
+                       if (bound())
                        {
                                expression_ = std::function<T ()>();
                                dependencies_.clear();
@@ -141,6 +194,8 @@ private:
                }
        };
 
+       template<typename> friend class binding;
+
        std::shared_ptr<impl> impl_;
 public:
        binding()
@@ -153,22 +208,39 @@ public:
        {
        }
 
-       binding(const std::function<T ()>& expression, const binding<T>& dep)
+       // Expr -> T ()
+       template<typename Expr>
+       explicit binding(const Expr& expression)
+               : impl_(new impl(expression))
+       {
+               //impl_->evaluate();
+       }
+
+       // Expr -> T ()
+       template<typename Expr, typename T2>
+       binding(const Expr& expression, const binding<T2>& dep)
                : impl_(new impl(expression))
        {
                depend_on(dep);
-               impl_->evaluate();
+               //impl_->evaluate();
        }
 
+       // Expr -> T ()
+       template<typename Expr, typename T2, typename T3>
        binding(
-                       const std::function<T ()>& expression,
-                       const binding<T>& dep1,
-                       const binding<T>& dep2)
+                       const Expr& expression,
+                       const binding<T2>& dep1,
+                       const binding<T3>& dep2)
                : impl_(new impl(expression))
        {
                depend_on(dep1);
                depend_on(dep2);
-               impl_->evaluate();
+               //impl_->evaluate();
+       }
+
+       void* identity() const
+       {
+               return impl_.get();
        }
 
        T get() const
@@ -191,43 +263,281 @@ public:
                return impl_->bound();
        }
 
-       void depend_on(const binding<T>& other)
+       template<typename T2>
+       void depend_on(const binding<T2>& other)
        {
                impl_->depend_on(other.impl_);
        }
 
        binding<T> operator+(T other) const
        {
+               return transformed([other](T self) { return self + other; });
+       }
+
+       binding<T> operator+(const binding<T>& other) const
+       {
+               return composed(other, [](T self, T o) { return self + o; });
+       }
+
+       binding<T>& operator++()
+       {
+               T new_value = get();
+               ++new_value;
+
+               set(new_value);
+
+               return *this;
+       }
+
+       binding<T> operator++(int)
+       {
+               binding<T> pre_val(get());
+               ++*this;
+
+               return pre_val;
+       }
+
+       binding<T> operator-() const
+       {
+               return transformed([](T self) { return -self; });
+       }
+
+       binding<T> operator-(const binding<T>& other) const
+       {
+               return *this + -other;
+       }
+
+       binding<T> operator-(T other) const
+       {
+               return *this + -other;
+       }
+
+       binding<T>& operator--()
+       {
+               T new_value = get();
+               --new_value;
+
+               set(new_value);
+
+               return *this;
+       }
+
+       binding<T> operator--(int)
+       {
+               binding<T> pre_val(get());
+               --*this;
+
+               return pre_val;
+       }
+
+       binding<T> operator*(T other) const
+       {
+               return transformed([other](T self) { return self * other; });
+       }
+
+       binding<T> operator*(const binding<T>& other) const
+       {
+               return composed(other, [](T self, T o) { return self * o; });
+       }
+
+       binding<T> operator/(T other) const
+       {
+               return transformed([other](T self) { return self / other; });
+       }
+
+       binding<T> operator/(const binding<T>& other) const
+       {
+               return composed(other, [](T self, T o) { return self / o; });
+       }
+
+       binding<T> operator%(T other) const
+       {
+               return transformed([other](T self) { return self % other; });
+       }
+
+       binding<T> operator%(const binding<T>& other) const
+       {
+               return composed(other, [](T self, T o) { return self % o; });
+       }
+
+       binding<bool> operator==(T other) const
+       {
+               return transformed([other](T self) { return self == other; });
+       }
+
+       binding<bool> operator==(const binding<T>& other) const
+       {
+               return composed(other, [](T self, T o) { return self == o; });
+       }
+
+       binding<bool> operator!=(T other) const
+       {
+               return transformed([other](T self) { return self != other; });
+       }
+
+       binding<bool> operator!=(const binding<T>& other) const
+       {
+               return composed(other, [](T self, T o) { return self != o; });
+       }
+
+       binding<bool> operator<(T other) const
+       {
+               return transformed([other](T self) { return self < other; });
+       }
+
+       binding<bool> operator<(const binding<T>& other) const
+       {
+               return composed(other, [](T self, T o) { return self < o; });
+       }
+
+       binding<bool> operator>(T other) const
+       {
+               return transformed([other](T self) { return self > other; });
+       }
+
+       binding<bool> operator>(const binding<T>& other) const
+       {
+               return composed(other, [](T self, T o) { return self > o; });
+       }
+
+       binding<bool> operator<=(T other) const
+       {
+               return transformed([other](T self) { return self <= other; });
+       }
+
+       binding<bool> operator<=(const binding<T>& other) const
+       {
+               return composed(other, [](T self, T o) { return self <= o; });
+       }
+
+       binding<bool> operator>=(T other) const
+       {
+               return transformed([other](T self) { return self >= other; });
+       }
+
+       binding<bool> operator>=(const binding<T>& other) const
+       {
+               return composed(other, [](T self, T o) { return self >= o; });
+       }
+
+       template<typename T2>
+       typename std::enable_if<
+                       std::is_same<T, T2>::value,
+                       binding<T2>
+               >::type as() const
+       {
+               return *this;
+       }
+
+       template<typename T2>
+       typename std::enable_if<
+                       (std::is_arithmetic<T>::value || std::is_same<bool, T>::value) && (std::is_arithmetic<T2>::value || std::is_same<bool, T2>::value) && !std::is_same<T, T2>::value,
+                       binding<T2>
+               >::type as() const
+       {
+               return transformed([](T val) { return static_cast<T2>(val); });
+       }
+
+       template<typename T2>
+       typename std::enable_if<
+                       (std::is_same<std::wstring, T>::value || std::is_same<std::wstring, T2>::value) && !std::is_same<T, T2>::value,
+                       binding<T2>
+               >::type as() const
+       {
+               return transformed([](T val) { return boost::lexical_cast<T2>(val); });
+       }
+
+       // Func -> R (T self_val)
+       // Returns binding<R>
+       template<typename Func>
+       auto transformed(const Func& func) const -> binding<decltype(func(impl_->value_))>
+       {
+               typedef decltype(func(impl_->value_)) R;
                auto self = impl_;
 
-               return binding<T>(
-                               [other, self] { return other + self->get(); },
-                               other);
+               return binding<R>(
+                               [self, func] { return func(self->get()); },
+                               *this);
        }
 
-       binding<T> operator+(const binding<T>& other) const
+       // Func -> R (T self_val, T2 other_val)
+       // Returns binding<R>
+       template<typename Func, typename T2>
+       auto composed(const binding<T2>& other, const Func& func) const -> binding<decltype(func(impl_->value_, other.impl_->value_))>
        {
+               typedef decltype(func(impl_->value_, other.impl_->value_)) R;
                auto self = impl_;
                auto o = other.impl_;
 
-               return binding<T>(
-                               [self, o] { return o->get() + self->get(); },
+               return binding<R>(
+                               [self, o, func] { return func(self->get(), o->get()); },
                                *this,
                                other);
        }
 
-       void unbind()
+       template<typename TweenerFunc, typename FrameCounter>
+       binding<T> animated(
+                       const binding<FrameCounter>& frame_counter,
+                       const binding<FrameCounter>& duration,
+                       const binding<TweenerFunc>& tweener_func) const
        {
-               impl_->unbind();
+               auto self                                       = impl_;
+               auto f                                          = frame_counter.impl_;
+               auto d                                          = duration.impl_;
+               auto tw                                         = tweener_func.impl_;
+               FrameCounter start_frame        = frame_counter.get();
+               FrameCounter current_frame      = start_frame;
+               T current_source                        = get();
+               T current_destination           = current_source;
+               T current_result                        = current_source;
+
+               auto result = binding<T>(
+                       [=] () mutable
+                       {
+                               auto frame_diff         = f->get() - current_frame;
+                               bool new_frame          = f->get() != current_frame;
+
+                               if (!new_frame)
+                                       return current_result;
+
+                               bool new_tween          = current_destination != self->get();
+                               auto time                       = current_frame - start_frame + (new_tween ? frame_diff : 0) + 1;
+                               auto dur                        = d->get();
+                               current_frame           = f->get();
+
+                               current_source          = new_tween ? tw->get()(time, current_source, current_destination - current_source, dur) : current_source;
+                               current_destination     = self->get();
+
+                               if (new_tween)
+                                       start_frame             = current_frame;
+
+                               time                            = current_frame - start_frame;
+
+                               if (time < dur)
+                                       current_result = tw->get()(time, current_source, current_destination - current_source, dur);
+                               else
+                               {
+                                       current_result = current_destination;
+                                       current_source = current_destination;
+                               }
+
+                               return current_result;
+                       });
+
+               result.depend_on(*this);
+               result.depend_on(frame_counter);
+               result.depend_on(tweener_func);
+
+               return result;
        }
 
-       binding<T> operator-(T other) const
+       void unbind()
        {
-               return add(-other);
+               impl_->unbind();
        }
 
        void on_change(
-                       const std::weak_ptr<void> dependant,
+                       const std::weak_ptr<void>& dependant,
                        const std::function<void ()>& listener) const
        {
                impl_->on_change(dependant, listener);
@@ -237,9 +547,9 @@ public:
                        const std::function<void ()>& listener) const
        {
                std::shared_ptr<void> subscription(new char);
-               
-               impl_->on_change(subscription, listener);
-               
+
+               on_change(subscription, listener);
+
                return subscription;
        }
 private:
@@ -249,4 +559,122 @@ private:
        }
 };
 
+static binding<bool> operator||(const binding<bool>& lhs, const binding<bool>& rhs)
+{
+       return lhs.composed(rhs, [](bool lhs, bool rhs) { return lhs || rhs; });
+}
+
+static binding<bool> operator||(const binding<bool>& lhs, bool rhs)
+{
+       return lhs.transformed([rhs](bool lhs) { return lhs || rhs; });
+}
+
+static binding<bool> operator&&(const binding<bool>& lhs, const binding<bool>& rhs)
+{
+       return lhs.composed(rhs, [](bool lhs, bool rhs) { return lhs && rhs; });
+}
+
+static binding<bool> operator&&(const binding<bool>& lhs, bool rhs)
+{
+       return lhs.transformed([rhs](bool lhs) { return lhs && rhs; });
+}
+
+static binding<bool> operator!(const binding<bool>& self)
+{
+       return self.transformed([](bool self) { return !self; });
+}
+
+template<typename T>
+class ternary_builder
+{
+       binding<bool> condition_;
+       binding<T> true_result_;
+public:
+       ternary_builder(
+                       const binding<bool>& condition, const binding<T>& true_result)
+               : condition_(condition)
+               , true_result_(true_result)
+       {
+       }
+
+       binding<T> otherwise(const binding<T>& false_result)
+       {
+               auto condition = condition_;
+               auto true_result = true_result_;
+
+               binding<T> result([condition, true_result, false_result]()
+               {
+                       return condition.get() ? true_result.get() : false_result.get();
+               });
+
+               result.depend_on(condition);
+               result.depend_on(true_result);
+               result.depend_on(false_result);
+
+               return result;
+       }
+
+       binding<T> otherwise(T false_result)
+       {
+               return otherwise(binding<T>(false_result));
+       }
+};
+
+class when
+{
+       binding<bool> condition_;
+public:
+       when(const binding<bool>& condition)
+               : condition_(condition)
+       {
+       }
+
+       template<typename T>
+       ternary_builder<T> then(const binding<T>& true_result)
+       {
+               return ternary_builder<T>(condition_, true_result);
+       }
+
+       template<typename T>
+       ternary_builder<T> then(T true_result)
+       {
+               return then(binding<T>(true_result));
+       }
+};
+
+/*template<typename T, typename T2>
+binding<T> add_tween(
+               const binding<T>& to_tween,
+               const binding<T2>& counter,
+               T destination_value,
+               T2 duration,
+               const std::wstring& easing)
+{
+       tweener tween(easing);
+
+       double start_val = to_tween.as<double>().get();
+       double destination_val = static_cast<double>(destination_value);
+       double start_time = counter.as<double>().get();
+       double dur = static_cast<double>(duration);
+
+       return when(counter < duration)
+               .then(counter.as<double>().transformed([=](double t)
+               {
+                       return tween(t - start_time, start_val, destination_val, dur);
+               }).as<T>())
+               .otherwise(destination_value);
+}*/
+
+template<typename T, typename T2>
+binding<T> delay(
+               const binding<T>& to_delay,
+               const binding<T>& after_delay,
+               const binding<T2>& counter,
+               T2 duration)
+{
+       return when(counter < duration)
+                       .then(to_delay)
+                       .otherwise(after_delay);
+}
+
 }}