11from __future__ import annotations
22
3+ from functools import lru_cache
34import math
45
56from typing import ClassVar
89from mcproto .buffer import Buffer
910from mcproto .protocol import StructFormat
1011from mcproto .types .abc import MCType
11- from attrs import define
12+ from attrs import define , field , Attribute , validators
1213from mcproto .protocol .utils import to_twos_complement
1314
1415
1516@define
1617class FixedBitset (MCType ):
17- """Represents a fixed-size bitset."""
18+ """Represents a fixed-size bitset.
19+
20+ The size of the bitset must be defined using the :meth:`of_size` method.
21+ Each :class:`FixedBitset` class is unique to its size, and the size must be defined before using the class.
22+
23+ :param data: The bits of the bitset.
24+ """
1825
1926 __BIT_COUNT : ClassVar [int ] = - 1
2027
21- data : bytearray
28+ @staticmethod
29+ def data_length_check (_self : FixedBitset , attribute : Attribute [bytearray ], value : bytearray ) -> None :
30+ """Check that the data length matches the bitset size.
31+
32+ :raises ValueError: If the data length doesn't match the bitset size.
33+ """
34+ if _self .__BIT_COUNT == - 1 :
35+ raise ValueError ("Bitset size is not defined." )
36+ if len (value ) != math .ceil (_self .__BIT_COUNT / 8 ):
37+ raise ValueError (f"Bitset size is { _self .__BIT_COUNT } , but data length is { len (value )} ." )
38+
39+ data : bytearray = field (validator = data_length_check .__get__ (object ))
2240
2341 @override
2442 def serialize_to (self , buf : Buffer ) -> None :
@@ -32,18 +50,14 @@ def deserialize(cls, buf: Buffer) -> FixedBitset:
3250 data = buf .read (math .ceil (cls .__BIT_COUNT / 8 ))
3351 return cls (data = data )
3452
35- @override
36- def validate (self ) -> None :
37- """Validate the bitset."""
38- if self .__BIT_COUNT == - 1 :
39- raise ValueError ("Bitset size is not defined." )
40- if len (self .data ) != math .ceil (self .__BIT_COUNT / 8 ):
41- raise ValueError (f"Bitset size is { len (self .data ) * 8 } , expected { self .__BIT_COUNT } ." )
42-
4353 @staticmethod
54+ @lru_cache (maxsize = None )
4455 def of_size (n : int ) -> type [FixedBitset ]:
4556 """Return a new FixedBitset class with the given size.
4657
58+ The result of this method is cached, so calling it multiple times with the same value will return the same
59+ class.
60+
4761 :param n: The size of the bitset.
4862 """
4963 new_class = type (f"FixedBitset{ n } " , (FixedBitset ,), {})
@@ -113,8 +127,17 @@ class Bitset(MCType):
113127 :param data: The bits of the bitset.
114128 """
115129
116- size : int
117- data : list [int ]
130+ @staticmethod
131+ def data_length_check (_self : Bitset , attribute : Attribute [list [int ]], value : list [int ]) -> None :
132+ """Check that the data length matches the bitset size.
133+
134+ :raises ValueError: If the data length doesn't match the bitset size.
135+ """
136+ if len (value ) != _self .size :
137+ raise ValueError (f"Bitset size is { _self .size } , but data length is { len (value )} ." )
138+
139+ size : int = field (validator = validators .gt (0 ))
140+ data : list [int ] = field (validator = data_length_check .__get__ (object ))
118141
119142 @override
120143 def serialize_to (self , buf : Buffer ) -> None :
@@ -129,12 +152,6 @@ def deserialize(cls, buf: Buffer) -> Bitset:
129152 data = [buf .read_value (StructFormat .LONGLONG ) for _ in range (size )]
130153 return cls (size = size , data = data )
131154
132- @override
133- def validate (self ) -> None :
134- """Validate the bitset."""
135- if self .size != len (self .data ):
136- raise ValueError (f"Bitset size is ({ self .size } ) doesn't match data size ({ len (self .data )} )." )
137-
138155 @classmethod
139156 def from_int (cls , n : int , size : int | None = None ) -> Bitset :
140157 """Return a new Bitset with the given integer value.
0 commit comments