Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 7 additions & 14 deletions src/spellbind/bool_values.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from __future__ import annotations

import operator
from abc import ABC
from typing import TypeVar, Generic, Callable
from typing import TypeVar, Generic

from spellbind.values import Value, DerivedValue, Constant, SimpleVariable
from spellbind.values import Value, OneToOneValue, Constant, SimpleVariable

_S = TypeVar('_S')

Expand All @@ -13,21 +14,13 @@ def logical_not(self) -> BoolValue:
return NotBoolValue(self)


class MappedBoolValue(Generic[_S], DerivedValue[_S, bool], BoolValue):
def __init__(self, value: Value[_S], transform: Callable[[_S], bool]) -> None:
self._transform = transform
super().__init__(value)

def transform(self, value: _S) -> bool:
return self._transform(value)
class OneToBoolValue(OneToOneValue[_S, bool], BoolValue, Generic[_S]):
pass


class NotBoolValue(DerivedValue[bool, bool], BoolValue):
class NotBoolValue(OneToOneValue[bool, bool], BoolValue):
def __init__(self, value: Value[bool]):
super().__init__(value)

def transform(self, value: bool) -> bool:
return not value
super().__init__(operator.not_, value)


class BoolConstant(BoolValue, Constant[bool]):
Expand Down
184 changes: 81 additions & 103 deletions src/spellbind/float_values.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from __future__ import annotations

import operator
from abc import ABC, abstractmethod
from abc import ABC
from typing import Generic, Callable, Sequence, TypeVar, overload

from typing_extensions import Self
from typing_extensions import TYPE_CHECKING

from spellbind.bool_values import BoolValue
from spellbind.values import Value, SimpleVariable, DerivedValue, DerivedValueBase, Constant, CombinedTwoValues
from spellbind.functions import clamp_float, multiply_all_floats
from spellbind.values import Value, SimpleVariable, OneToOneValue, DerivedValueBase, Constant, TwoToOneValue

if TYPE_CHECKING:
from spellbind.int_values import IntValue, IntLike # pragma: no cover
Expand Down Expand Up @@ -102,13 +103,8 @@ def clamp(self, min_value: FloatLike, max_value: FloatLike) -> FloatValue:
return ClampFloatValue(self, min_value, max_value)


class MappedFloatValue(Generic[_S], DerivedValue[_S, float], FloatValue):
def __init__(self, value: Value[_S], transform: Callable[[_S], float]) -> None:
self._transform = transform
super().__init__(value)

def transform(self, value: _S) -> float:
return self._transform(value)
class OneToFloatValue(Generic[_S], OneToOneValue[_S, float], FloatValue):
pass


class FloatConstant(FloatValue, Constant[float]):
Expand All @@ -119,141 +115,123 @@ class FloatVariable(SimpleVariable[float], FloatValue):
pass


def _get_float(value: float | Value[int] | Value[float]) -> float:
def _create_float_getter(value: float | Value[int] | Value[float]) -> Callable[[], float]:
if isinstance(value, Value):
return value.value
return lambda: value.value
else:
return value
return lambda: value


class CombinedFloatValues(DerivedValueBase[_U], Generic[_U], ABC):
def __init__(self, *values: float | Value[int] | Value[float]):
super().__init__(*[v for v in values if isinstance(v, Value)])
self._gotten_values = [_get_float(v) for v in values]
self._callbacks: list[Callable] = []
for i, v in enumerate(values):
if isinstance(v, Value):
v.weak_observe(self._create_on_n_changed(i))
self._value = self._calculate_value()

def _create_on_n_changed(self, index: int) -> Callable[[float], None]:
def on_change(new_value: float) -> None:
self._gotten_values[index] = new_value
self._on_result_change(self._calculate_value())
self._callbacks.append(on_change) # keep strong reference to callback so it won't be garbage collected
return on_change

def _calculate_value(self) -> _U:
return self.transform(self._gotten_values)

def _on_result_change(self, new_value: _U) -> None:
if new_value != self._value:
self._value = new_value
self._on_change(self._value)

@abstractmethod
def transform(self, values: Sequence[float]) -> _U: ...
class OneFloatToOneValue(DerivedValueBase[_S], Generic[_S]):
def __init__(self, transformer: Callable[[float], _S], of: FloatLike):
self._getter = _create_float_getter(of)
self._transformer = transformer
super().__init__(*[v for v in (of,) if isinstance(v, Value)])

@property
def value(self) -> _U:
def value(self) -> _S:
return self._value

def _calculate_value(self) -> _S:
return self._transformer(self._getter())

class MaxFloatValues(CombinedFloatValues[float], FloatValue):
def transform(self, values: Sequence[float]) -> float:
return max(values)

class ManyFloatToOneValue(DerivedValueBase[_S], Generic[_S]):
def __init__(self, transformer: Callable[[Sequence[float]], _S], *values: FloatLike):
self._value_getters = [_create_float_getter(v) for v in values]
self._transformer = transformer
super().__init__(*[v for v in values if isinstance(v, Value)])

class MinFloatValues(CombinedFloatValues[float], FloatValue):
def transform(self, values: Sequence[float]) -> float:
return min(values)
def _calculate_value(self) -> _S:
gotten_values = [getter() for getter in self._value_getters]
return self._transformer(gotten_values)


class CombinedTwoFloatValues(CombinedFloatValues[_U], Generic[_U], ABC):
def __init__(self, left: FloatLike, right: FloatLike):
super().__init__(left, right)
class TwoFloatToOneValue(DerivedValueBase[_S], Generic[_S]):
def __init__(self, transformer: Callable[[float, float], _S],
first: FloatLike, second: FloatLike):
self._transformer = transformer
self._first_getter = _create_float_getter(first)
self._second_getter = _create_float_getter(second)
super().__init__(*[v for v in (first, second) if isinstance(v, Value)])

def transform(self, values: Sequence[float]) -> _U:
return self.transform_two(values[0], values[1])
def _calculate_value(self) -> _S:
return self._transformer(self._first_getter(), self._second_getter())

@abstractmethod
def transform_two(self, left: float, right: float) -> _U: ...

class ThreeFloatToOneValue(DerivedValueBase[_S], Generic[_S]):
def __init__(self, transformer: Callable[[float, float, float], _S],
first: FloatLike, second: FloatLike, third: FloatLike):
self._transformer = transformer
self._first_getter = _create_float_getter(first)
self._second_getter = _create_float_getter(second)
self._third_getter = _create_float_getter(third)
super().__init__(*[v for v in (first, second, third) if isinstance(v, Value)])

class CombinedThreeFloatValues(CombinedFloatValues[_U], Generic[_U], ABC):
def __init__(self, left: FloatLike, middle: FloatLike, right: FloatLike):
super().__init__(left, middle, right)
def _calculate_value(self) -> _S:
return self._transformer(self._first_getter(), self._second_getter(), self._third_getter())

def transform(self, values: Sequence[float]) -> _U:
return self.transform_three(values[0], values[1], values[2])

@abstractmethod
def transform_three(self, left: float, middle: float, right: float) -> _U: ...
class MaxFloatValues(ManyFloatToOneValue[float], FloatValue):
def __init__(self, *values: FloatLike):
super().__init__(max, *values)


class AddFloatValues(CombinedFloatValues[float], FloatValue):
def transform(self, values: Sequence[float]) -> float:
return sum(values)
class MinFloatValues(ManyFloatToOneValue[float], FloatValue):
def __init__(self, *values: FloatLike):
super().__init__(min, *values)


class SubtractFloatValues(CombinedTwoFloatValues[float], FloatValue):
def transform_two(self, left: float, right: float) -> float:
return left - right
class AddFloatValues(ManyFloatToOneValue[float], FloatValue):
def __init__(self, *values: FloatLike):
super().__init__(sum, *values)


class MultiplyFloatValues(CombinedFloatValues[float], FloatValue):
def transform(self, values: Sequence[float]) -> float:
result = 1.0
for value in values:
result *= value
return result
class SubtractFloatValues(TwoFloatToOneValue[float], FloatValue):
def __init__(self, left: FloatLike, right: FloatLike):
super().__init__(operator.sub, left, right)


class DivideValues(CombinedTwoFloatValues[float], FloatValue):
def transform_two(self, left: float, right: float) -> float:
return left / right
class MultiplyFloatValues(ManyFloatToOneValue[float], FloatValue):
def __init__(self, *values: FloatLike):
super().__init__(multiply_all_floats, *values)


class RoundFloatValue(CombinedTwoValues[float, int, float], FloatValue):
class RoundFloatValue(TwoToOneValue[float, int, float], FloatValue):
def __init__(self, value: FloatValue, ndigits: IntLike):
super().__init__(value, ndigits)
super().__init__(round, value, ndigits)

def transform(self, value: float, ndigits: int) -> float:
return round(value, ndigits)


class ModuloFloatValues(CombinedTwoFloatValues[float], FloatValue):
def transform_two(self, left: float, right: float) -> float:
return left % right
class DivideValues(TwoFloatToOneValue[float], FloatValue):
def __init__(self, left: FloatLike, right: FloatLike):
super().__init__(operator.truediv, left, right)


class AbsFloatValue(DerivedValue[float, float], FloatValue):
def transform(self, value: float) -> float:
return abs(value)
class ModuloFloatValues(TwoFloatToOneValue[float], FloatValue):
def __init__(self, left: FloatLike, right: FloatLike):
super().__init__(operator.mod, left, right)


class PowerFloatValues(CombinedTwoFloatValues[float], FloatValue):
def transform_two(self, left: float, right: float) -> float:
return left ** right
class AbsFloatValue(OneFloatToOneValue[float], FloatValue):
def __init__(self, value: FloatLike):
super().__init__(abs, value)


class NegateFloatValue(DerivedValue[float, float], FloatValue):
def transform(self, value: float) -> float:
return -value
class PowerFloatValues(TwoFloatToOneValue[float], FloatValue):
def __init__(self, left: FloatLike, right: FloatLike):
super().__init__(operator.pow, left, right)


class CompareNumbersValues(CombinedTwoFloatValues[bool], BoolValue):
def __init__(self, left: FloatLike, right: FloatLike, op: Callable[[float, float], bool]):
self._op = op
super().__init__(left, right)
class NegateFloatValue(OneFloatToOneValue[float], FloatValue):
def __init__(self, value: FloatLike):
super().__init__(operator.neg, value)

def transform_two(self, left: float, right: float) -> bool:
return self._op(left, right)

class CompareNumbersValues(TwoFloatToOneValue[bool], BoolValue):
def __init__(self, left: FloatLike, right: FloatLike, op: Callable[[float, float], bool]):
super().__init__(op, left, right)

class ClampFloatValue(CombinedThreeFloatValues[float], FloatValue):
def __init__(self, value: FloatLike, min_value: FloatLike, max_value: FloatLike) -> None:
super().__init__(value, min_value, max_value)

def transform_three(self, value: float, min_value: float, max_value: float) -> float:
return max(min_value, min(max_value, value))
class ClampFloatValue(ThreeFloatToOneValue[float], FloatValue):
def __init__(self, value: FloatLike, min_value: FloatLike, max_value: FloatLike):
super().__init__(clamp_float, value, min_value, max_value)
32 changes: 31 additions & 1 deletion src/spellbind/functions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import inspect
from inspect import Parameter
from typing import Callable
from typing import Callable, Sequence


def _is_positional_parameter(param: Parameter) -> bool:
Expand Down Expand Up @@ -31,3 +31,33 @@ def assert_parameter_max_count(callable_: Callable, max_count: int) -> None:
callable_name = str(callable_) # pragma: no cover
raise ValueError(f"Callable {callable_name} has too many non-default parameters: "
f"{count_non_default_parameters(callable_)} > {max_count}")


def multiply_all_ints(vals: Sequence[int]) -> int:
result = 1
for val in vals:
result *= val
return result


def multiply_all_floats(vals: Sequence[float]) -> float:
result = 1.
for val in vals:
result *= val
return result


def clamp_int(value: int, min_value: int, max_value: int) -> int:
if value < min_value:
return min_value
elif value > max_value:
return max_value
return value


def clamp_float(value: float, min_value: float, max_value: float) -> float:
if value < min_value:
return min_value
elif value > max_value:
return max_value
return value
Loading