Skip to content

Commit 4dc5dde

Browse files
committed
magicbot: Refactor tunable to be a descriptor
This refactors tunable to be a single descriptor class rather than using closures and properties, and uses slightly less indirection. Brief testing shows that this improves performance (ever so slightly). Breaking change: This removes the doc argument to tunable.
1 parent 5d5c87d commit 4dc5dde

File tree

2 files changed

+51
-42
lines changed

2 files changed

+51
-42
lines changed

magicbot/magic_tunable.py

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
11
import functools
22
import inspect
3+
from typing import Generic, Optional, TypeVar
34

45
from networktables import NetworkTables
56
from ntcore.value import Value
67

7-
# Only used as a marker
8-
class _TunableProperty(property):
9-
pass
8+
V = TypeVar("V")
109

1110

12-
class _AutosendProperty(_TunableProperty):
13-
pass
14-
15-
16-
def tunable(default, *, writeDefault=True, subtable=None, doc=None):
11+
class tunable(Generic[V]):
1712
"""
1813
This allows you to define simple properties that allow you to easily
1914
communicate with other programs via NetworkTables.
@@ -23,18 +18,17 @@ def tunable(default, *, writeDefault=True, subtable=None, doc=None):
2318
2419
class MyRobot(magicbot.MagicRobot):
2520
26-
my_component = MyComponent
21+
my_component: MyComponent
2722
28-
...
23+
...
2924
3025
from magicbot import tunable
3126
3227
class MyComponent:
3328
3429
# define the tunable property
3530
foo = tunable(True)
36-
37-
31+
3832
def execute(self):
3933
4034
# set the variable
@@ -65,33 +59,48 @@ def execute(self):
6559
# the name of the key is related to the name of the variable name in the
6660
# robot class
6761

68-
nt = NetworkTables
69-
mkv = Value.getFactory(default)
70-
71-
def _get(self):
72-
return getattr(self, prop._ntattr).value
73-
74-
def _set(self, value):
75-
v = getattr(self, prop._ntattr)
76-
nt._api.setEntryValueById(v._local_id, mkv(value))
77-
78-
prop = _TunableProperty(fget=_get, fset=_set, doc=doc)
79-
prop._ntdefault = default
80-
prop._ntsubtable = subtable
81-
prop._ntwritedefault = writeDefault
82-
83-
return prop
84-
85-
86-
def setup_tunables(component, cname, prefix="components"):
62+
__slots__ = (
63+
"_ntdefault",
64+
"_ntsubtable",
65+
"_ntwritedefault",
66+
# "__doc__",
67+
"_mkv",
68+
"_nt",
69+
)
70+
71+
def __init__(
72+
self,
73+
default: V,
74+
*,
75+
writeDefault: bool = True,
76+
subtable: Optional[str] = None
77+
# , doc: Optional[str] = None
78+
) -> None:
79+
self._ntdefault = default
80+
self._ntsubtable = subtable
81+
self._ntwritedefault = writeDefault
82+
# self.__doc__ = doc
83+
84+
self._mkv = Value.getFactory(default)
85+
self._nt = NetworkTables
86+
87+
def __get__(self, instance, owner) -> V:
88+
if instance is not None:
89+
return instance._tunables[self].value
90+
return self
91+
92+
def __set__(self, instance, value) -> None:
93+
v = instance._tunables[self]
94+
self._nt._api.setEntryValueById(v._local_id, self._mkv(value))
95+
96+
97+
def setup_tunables(component, cname: str, prefix: Optional[str] = "components") -> None:
8798
"""
8899
Connects the tunables on an object to NetworkTables.
89100
90101
:param component: Component object
91102
:param cname: Name of component
92-
:type cname: str
93103
:param prefix: Prefix to use, or no prefix if None
94-
:type prefix: str
95104
96105
.. note:: This is not needed in normal use, only useful
97106
for testing
@@ -104,27 +113,27 @@ def setup_tunables(component, cname, prefix="components"):
104113
else:
105114
prefix = "/%s/%s" % (prefix, cname)
106115

116+
tunables = {}
117+
107118
for n in dir(cls):
108119
if n.startswith("_"):
109120
continue
110121

111122
prop = getattr(cls, n)
112-
if not isinstance(prop, _TunableProperty):
123+
if not isinstance(prop, tunable):
113124
continue
114125

115126
if prop._ntsubtable:
116127
key = "%s/%s/%s" % (prefix, prop._ntsubtable, n)
117128
else:
118129
key = "%s/%s" % (prefix, n)
119130

120-
ntattr = "_Tunable__%s" % n
121-
122131
ntvalue = NetworkTables.getGlobalAutoUpdateValue(
123132
key, prop._ntdefault, prop._ntwritedefault
124133
)
125-
# double indirection
126-
setattr(component, ntattr, ntvalue)
127-
prop._ntattr = ntattr
134+
tunables[prop] = ntvalue
135+
136+
component._tunables = tunables
128137

129138

130139
def feedback(f=None, *, key: str = None):

magicbot/magicrobot.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from robotpy_ext.misc.orderedclass import OrderedClass
1414
from robotpy_ext.misc.annotations import get_class_annotations
1515

16-
from .magic_tunable import setup_tunables, _TunableProperty, collect_feedbacks
16+
from .magic_tunable import setup_tunables, tunable, collect_feedbacks
1717
from .magic_reset import will_reset_to
1818

1919
__all__ = ["MagicRobot"]
@@ -514,7 +514,7 @@ def _create_components(self):
514514

515515
# - Iterate over set class variables
516516
for m in self.members:
517-
if m.startswith("_") or isinstance(getattr(cls, m, None), _TunableProperty):
517+
if m.startswith("_") or isinstance(getattr(cls, m, None), tunable):
518518
continue
519519

520520
ctyp = getattr(self, m)
@@ -533,7 +533,7 @@ def _create_components(self):
533533
if (
534534
n.startswith("_")
535535
or n in self._exclude_from_injection
536-
or isinstance(getattr(cls, n, None), _TunableProperty)
536+
or isinstance(getattr(cls, n, None), tunable)
537537
):
538538
continue
539539

0 commit comments

Comments
 (0)