Skip to content

Commit

Permalink
Improve conn handling and limit RAM use (#476)
Browse files Browse the repository at this point in the history
  • Loading branch information
randallfrank authored Nov 13, 2024
1 parent 82bfde1 commit 60724b2
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 18 deletions.
16 changes: 16 additions & 0 deletions src/ansys/pyensight/core/utils/dsg_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,9 @@ def __init__(
self._dsg_queue: Optional[queue.SimpleQueue] = None # Outgoing messages to EnSight
self._shutdown = False
self._dsg = None
# Prevent the protobuffer queue from growing w/o limits. The payload chunking is
# around 4MB, so 200 buffers would be a bit less than 1GB.
self._max_dsg_queue_size = int(os.environ.get("ANSYS_OV_SERVER_MAX_GRPC_QUEUE_SIZE", "200"))
self._normalize_geometry = normalize_geometry
self._vrmode = vrmode
self._time_scale = time_scale
Expand All @@ -711,6 +714,14 @@ def scene_bounds(self) -> Optional[List]:
def mesh_block_count(self) -> int:
return self._mesh_block_count

@property
def max_dsg_queue_size(self) -> int:
return self._max_dsg_queue_size

@max_dsg_queue_size.setter
def max_dsg_queue_size(self, value: int) -> None:
self._max_dsg_queue_size = value

@property
def vrmode(self) -> bool:
return self._vrmode
Expand Down Expand Up @@ -891,6 +902,11 @@ def _poll_messages(self) -> None:
while not self._shutdown:
try:
self._message_queue.put(next(self._dsg)) # type:ignore
# if the queue is getting too deep, wait a bit to avoid holding too
# many messages (filling up memory)
if self.max_dsg_queue_size:
while self._message_queue.qsize() >= self.max_dsg_queue_size:
time.sleep(0.001)
except Exception:
self._shutdown = True
self.log("DSG connection broken, calling exit")
Expand Down
159 changes: 141 additions & 18 deletions src/ansys/pyensight/core/utils/omniverse_glb_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,19 @@ def _parse_mesh(self, meshid: int, parentid: int, parentname: str) -> None:
"""
mesh = self._gltf.meshes[meshid]
for prim_idx, prim in enumerate(mesh.primitives):
# POINTS, LINES, LINE_LOOP, LINE_STRIP, TRIANGLES, TRIANGLE_STRIP, TRIANGLE_FAN
# TODO: line width/point size
# POINTS, LINES, TRIANGLES, LINE_LOOP, LINE_STRIP, TRIANGLE_STRIP, TRIANGLE_FAN
mode = prim.mode
if mode not in (pygltflib.TRIANGLES, pygltflib.LINES, pygltflib.POINTS):
self.warn(
f"Unhandled connectivity {mode}. Currently only TRIANGLE and LINE connectivity is supported."
)
if mode not in (
pygltflib.TRIANGLES,
pygltflib.LINES,
pygltflib.POINTS,
pygltflib.LINE_LOOP,
pygltflib.LINE_STRIP,
pygltflib.TRIANGLE_STRIP,
pygltflib.TRIANGLE_FAN,
):
self.warn(f"Unhandled connectivity detected: {mode}. Geometry skipped.")
continue
glb_materialid = prim.material

Expand All @@ -151,28 +158,47 @@ def _parse_mesh(self, meshid: int, parentid: int, parentname: str) -> None:
self._handle_update_command(cmd)

# GLB Attributes -> DSG Geom
conn = self._get_data(prim.indices, 0)
cmd, conn_pb = self._create_pb("GEOM", parent_id=part_dsg_id)
if mode == pygltflib.TRIANGLES:
conn_pb.payload_type = dynamic_scene_graph_pb2.UpdateGeom.ArrayType.TRIANGLES
elif mode == pygltflib.LINES:
conn_pb.payload_type = dynamic_scene_graph_pb2.UpdateGeom.ArrayType.LINES
else:
conn_pb.payload_type = dynamic_scene_graph_pb2.UpdateGeom.ArrayType.POINTS
conn_pb.int_array.extend(conn)
conn_pb.chunk_offset = 0
conn_pb.total_array_size = len(conn)
self._handle_update_command(cmd)

# Verts
num_verts = 0
if prim.attributes.POSITION is not None:
verts = self._get_data(prim.attributes.POSITION)
num_verts = len(verts) // 3
cmd, verts_pb = self._create_pb("GEOM", parent_id=part_dsg_id)
verts_pb.payload_type = dynamic_scene_graph_pb2.UpdateGeom.ArrayType.COORDINATES
verts_pb.flt_array.extend(verts)
verts_pb.chunk_offset = 0
verts_pb.total_array_size = len(verts)
self._handle_update_command(cmd)

# Connectivity
if num_verts and (mode != pygltflib.POINTS):
if prim.indices is not None:
conn = self._get_data(prim.indices, 0)
else:
conn = numpy.array(list(range(num_verts)), dtype=numpy.uint32)
cmd, conn_pb = self._create_pb("GEOM", parent_id=part_dsg_id)
if mode == pygltflib.TRIANGLES:
conn_pb.payload_type = dynamic_scene_graph_pb2.UpdateGeom.ArrayType.TRIANGLES
elif mode == pygltflib.TRIANGLE_STRIP:
conn_pb.payload_type = dynamic_scene_graph_pb2.UpdateGeom.ArrayType.TRIANGLES
conn = self._tri_strip_to_tris(conn)
elif mode == pygltflib.TRIANGLE_FAN:
conn_pb.payload_type = dynamic_scene_graph_pb2.UpdateGeom.ArrayType.TRIANGLES
conn = self._tri_fan_to_tris(conn)
elif mode == pygltflib.LINES:
conn_pb.payload_type = dynamic_scene_graph_pb2.UpdateGeom.ArrayType.LINES
elif mode == pygltflib.LINE_LOOP:
conn_pb.payload_type = dynamic_scene_graph_pb2.UpdateGeom.ArrayType.LINES
conn = self._line_loop_to_lines(conn)
elif mode == pygltflib.LINE_STRIP:
conn_pb.payload_type = dynamic_scene_graph_pb2.UpdateGeom.ArrayType.LINES
conn = self._line_strip_to_lines(conn)
conn_pb.int_array.extend(conn)
conn_pb.chunk_offset = 0
conn_pb.total_array_size = len(conn)
self._handle_update_command(cmd)

# Normals
if prim.attributes.NORMAL is not None:
normals = self._get_data(prim.attributes.NORMAL)
cmd, normals_pb = self._create_pb("GEOM", parent_id=part_dsg_id)
Expand All @@ -199,6 +225,103 @@ def _parse_mesh(self, meshid: int, parentid: int, parentname: str) -> None:
texcoords_pb.variable_id = glb_varid
self._handle_update_command(cmd)

@staticmethod
def _tri_strip_to_tris(conn: numpy.ndarray) -> numpy.ndarray:
"""
Convert GL_TRIANGLE_STRIP connectivity into GL_TRIANGLES
Parameters
----------
conn: numpy.ndarray
The tri strip connectivity
Returns
-------
numpy.array:
Triangles connectivity
"""
tris = []
swap = False
for i in range(len(conn) - 2):
tris.append(conn[i])
if swap:
tris.append(conn[i + 2])
tris.append(conn[i + 1])
else:
tris.append(conn[i + 1])
tris.append(conn[i + 2])
swap = not swap
return numpy.array(tris, dtype=conn.dtype)

@staticmethod
def _tri_fan_to_tris(conn: numpy.ndarray) -> numpy.ndarray:
"""
Convert GL_TRIANGLE_FAN connectivity into GL_TRIANGLES
Parameters
----------
conn: numpy.ndarray
The fan connectivity
Returns
-------
numpy.array:
Triangles connectivity
"""
tris = []
for i in range(1, len(conn) - 1):
tris.append(conn[0])
tris.append(conn[i])
tris.append(conn[i + 1])
return numpy.array(tris, dtype=conn.dtype)

@staticmethod
def _line_strip_to_lines(conn) -> numpy.ndarray:
"""
Convert GL_LINE_STRIP connectivity into GL_LINES
Parameters
----------
conn: numpy.ndarray
The line strip connectivity
Returns
-------
numpy.array:
Lines connectivity
"""
lines = []
num_nodes = len(conn)
for i in range(num_nodes - 1):
lines.append(conn[i])
lines.append(conn[i + 1])
return numpy.array(lines, dtype=conn.dtype)

@staticmethod
def _line_loop_to_lines(conn) -> numpy.ndarray:
"""
Convert GL_LINE_LOOP connectivity into GL_LINES
Parameters
----------
conn: numpy.ndarray
The line loop connectivity
Returns
-------
numpy.array:
Lines connectivity
"""
lines = []
num_nodes = len(conn)
for i in range(num_nodes):
lines.append(conn[i])
if i + 1 == num_nodes:
lines.append(conn[0])
else:
lines.append(conn[i + 1])
return numpy.array(lines, dtype=conn.dtype)

def _get_data(
self,
accessorid: int,
Expand Down

0 comments on commit 60724b2

Please sign in to comment.