ailloli_ui_runtime/layout/layout_ctx.rs
1//! Mutable layout context shared while traversing retained nodes.
2
3#[cfg(feature = "devtools")]
4use ailloli_ui_core::geometry::Size;
5use ailloli_ui_core::geometry::{Constraints, Rect};
6use ailloli_ui_core::ids::ElementId;
7use ailloli_ui_core::math::Scale;
8use ailloli_ui_text::TextSystem;
9use std::cell::RefCell;
10use std::collections::{HashMap, HashSet};
11use std::rc::Rc;
12
13use super::layout_attempt::{LayoutAttempt, LayoutAttemptToken};
14use super::layout_engine::LayoutEngine;
15#[cfg(feature = "devtools")]
16use super::layout_result::LayoutDebugInfo;
17use super::layout_result::LayoutResult;
18use crate::component::reactive::{ReactiveDependencyBatchResult, ReactiveDependencyUpdate};
19
20/// Erased runtime callback used to atomically publish committed layout reads.
21type ReactiveLayoutPublisher =
22 Rc<dyn Fn(&[ReactiveDependencyUpdate]) -> ReactiveDependencyBatchResult>;
23
24/// Erased runtime callback used when a layout attempt observes stale input.
25type ReactiveLayoutRetry = Rc<dyn Fn(ElementId)>;
26
27/// Erased runtime callback used when a whole staging overlay is discarded.
28type ReactiveLayoutAbandon = Rc<dyn Fn()>;
29
30/// Authority of the geometry produced by the current layout traversal.
31///
32/// Measurement is speculative and must not commit persistent state derived
33/// from the temporary geometry. Commit passes use the allocation retained by
34/// the parent and may evaluate geometry-dependent effects. A measurement pass
35/// is sticky: descendants cannot regain commit authority locally.
36///
37/// # Examples
38///
39/// ```
40/// use ailloli_ui_runtime::layout::LayoutPass;
41///
42/// assert!(LayoutPass::Measure.is_measure());
43/// assert!(LayoutPass::Commit.is_committed());
44/// ```
45#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
46pub enum LayoutPass {
47 /// Speculative intrinsic-size or allocation probe.
48 Measure,
49 /// Authoritative retained allocation.
50 #[default]
51 Commit,
52}
53
54/// Provides phase predicates and sticky descendant composition.
55impl LayoutPass {
56 /// Returns `true` for speculative measurement geometry.
57 pub const fn is_measure(self) -> bool {
58 matches!(self, Self::Measure)
59 }
60
61 /// Returns `true` for authoritative committed geometry.
62 pub const fn is_committed(self) -> bool {
63 matches!(self, Self::Commit)
64 }
65
66 /// Combines an ancestor pass with a locally requested child pass.
67 const fn descend(self, requested: Self) -> Self {
68 if self.is_measure() || requested.is_measure() {
69 Self::Measure
70 } else {
71 Self::Commit
72 }
73 }
74}
75
76/// Mutable services and scoped hints available during one layout traversal.
77///
78/// Geometry is expressed in logical pixels. The context borrows its optional
79/// text system exclusively, so it is local to the current thread and layout
80/// pass rather than a shareable global service.
81///
82/// # Examples
83///
84/// ```
85/// use ailloli_ui_core::Scale;
86/// use ailloli_ui_runtime::layout::LayoutContext;
87///
88/// let ctx = LayoutContext::new(Scale::new(2.0));
89/// assert_eq!(ctx.scale.dpr, 2.0);
90/// assert!(ctx.text_system.is_none());
91/// ```
92pub struct LayoutContext<'a> {
93 /// Device-pixel ratio used for snapping and layout-cache identity.
94 pub scale: Scale,
95 /// Shared text layout engine (optional for text-free tests).
96 pub text_system: Option<&'a mut TextSystem>,
97 /// Optional content-local viewport propagated by a virtualizing ancestor.
98 virtual_viewport: Option<VirtualViewport>,
99 /// Current geometry authority, inherited monotonically by descendants.
100 layout_pass: LayoutPass,
101 /// Explicit speculative branches adopted by the current authoritative attempt.
102 measure_branches: Rc<RefCell<MeasureBranchState>>,
103 /// Innermost explicit speculative branch, if one is active.
104 current_measure_branch: Option<u64>,
105 /// Runtime-owned writes staged by the current outer layout call.
106 layout_attempt: Option<LayoutAttempt>,
107 /// Exact successful attempt scoped only while post-layout hooks execute.
108 committed_layout_attempt: Option<LayoutAttemptToken>,
109 /// Optional retained dependency publisher installed by `Runtime`.
110 reactive_layout_publisher: Option<ReactiveLayoutPublisher>,
111 /// Optional targeted retry scheduler installed by `Runtime`.
112 reactive_layout_retry: Option<ReactiveLayoutRetry>,
113 /// Optional internal diagnostic callback for discarded staging overlays.
114 reactive_layout_abandon: Option<ReactiveLayoutAbandon>,
115 #[cfg(feature = "devtools")]
116 /// Latest developer-tooling layout record for each element in this context.
117 pub debug_layouts: HashMap<ElementId, LayoutDebugInfo>,
118}
119
120/// Bookkeeping shared with explicit measurement tokens.
121#[derive(Default)]
122struct MeasureBranchState {
123 /// Last allocated nonzero branch identity.
124 next_id: u64,
125 /// Direct parent of every branch allocated by the active attempt.
126 parents: HashMap<u64, Option<u64>>,
127 /// Branches explicitly accepted by their owner widget.
128 adopted: HashSet<u64>,
129}
130
131/// Result of an explicit speculative layout branch.
132///
133/// Dropping this token abandons every measurement performed by the branch.
134/// Call [`Self::adopt`] only when the result contributes to the authoritative
135/// geometry being returned by the widget. Existing widgets that use
136/// [`LayoutCtx::with_layout_pass`] retain their historical auto-adopt behavior.
137#[doc(hidden)]
138#[must_use = "dropping a measurement branch abandons its reactive dependencies"]
139pub struct LayoutMeasurement<T> {
140 /// Branch result, taken exactly once by `adopt`.
141 value: Option<T>,
142 /// Nonzero identity recorded by staged measurement entries.
143 branch_id: u64,
144 /// Shared acceptance set owned by the layout context.
145 state: Rc<RefCell<MeasureBranchState>>,
146}
147
148impl<T> LayoutMeasurement<T> {
149 /// Borrows the speculative result before deciding whether to adopt it.
150 #[doc(hidden)]
151 pub fn result(&self) -> &T {
152 self.value
153 .as_ref()
154 .expect("layout measurement result was already adopted")
155 }
156
157 /// Accepts this branch and returns its result.
158 ///
159 /// The authoritative layout attempt will publish the branch's reactive
160 /// reads only if that complete attempt also succeeds and remains current.
161 #[doc(hidden)]
162 pub fn adopt(mut self) -> T {
163 self.state.borrow_mut().adopted.insert(self.branch_id);
164 self.value
165 .take()
166 .expect("layout measurement result was already adopted")
167 }
168}
169
170/// Viewport made available to widgets whose content is larger than the
171/// visible scroll container. Coordinates are local to the content.
172///
173/// Values use logical pixels and are stored verbatim. In particular, negative
174/// or non-finite overscan is not clamped here; consumers must define how such
175/// values affect virtualization.
176///
177/// # Examples
178///
179/// ```
180/// use ailloli_ui_core::Rect;
181/// use ailloli_ui_runtime::layout::VirtualViewport;
182///
183/// let viewport = VirtualViewport::new(Rect::new(0.0, 120.0, 640.0, 480.0), 32.0);
184/// assert_eq!(viewport.overscan, 32.0);
185/// ```
186#[derive(Debug, Clone, Copy, PartialEq)]
187pub struct VirtualViewport {
188 /// Visible rectangle in content-local logical pixels.
189 pub rect: Rect,
190 /// Extra logical pixels requested around every visible edge.
191 pub overscan: f32,
192}
193
194/// Provides the operations defined for VirtualViewport.
195impl VirtualViewport {
196 /// Creates a viewport without validating its rectangle or overscan.
197 ///
198 /// # Examples
199 ///
200 /// ```
201 /// use ailloli_ui_core::Rect;
202 /// use ailloli_ui_runtime::layout::VirtualViewport;
203 ///
204 /// let viewport = VirtualViewport::new(Rect::new(10.0, 20.0, 30.0, 40.0), 8.0);
205 /// assert_eq!(viewport.rect.y, 20.0);
206 /// ```
207 pub const fn new(rect: Rect, overscan: f32) -> Self {
208 Self { rect, overscan }
209 }
210}
211
212/// Short alias for [`LayoutContext`].
213///
214/// # Examples
215///
216/// ```
217/// use ailloli_ui_core::Scale;
218/// use ailloli_ui_runtime::layout::LayoutCtx;
219///
220/// let ctx = LayoutCtx::new(Scale::new(1.0));
221/// assert_eq!(ctx.scale.dpr, 1.0);
222/// ```
223pub type LayoutCtx<'a> = LayoutContext<'a>;
224
225/// Provides the operations defined for `LayoutContext<'a>`.
226impl<'a> LayoutContext<'a> {
227 /// Creates a context without a text system or virtual viewport.
228 ///
229 /// With `devtools`, the debug-record map also starts empty.
230 ///
231 /// # Examples
232 ///
233 /// ```
234 /// use ailloli_ui_core::Scale;
235 /// use ailloli_ui_runtime::layout::LayoutContext;
236 ///
237 /// let ctx = LayoutContext::new(Scale::new(1.25));
238 /// assert!(ctx.virtual_viewport().is_none());
239 /// ```
240 pub fn new(scale: Scale) -> Self {
241 Self {
242 scale,
243 text_system: None,
244 virtual_viewport: None,
245 layout_pass: LayoutPass::Commit,
246 measure_branches: Rc::new(RefCell::new(MeasureBranchState::default())),
247 current_measure_branch: None,
248 layout_attempt: None,
249 committed_layout_attempt: None,
250 reactive_layout_publisher: None,
251 reactive_layout_retry: None,
252 reactive_layout_abandon: None,
253 #[cfg(feature = "devtools")]
254 debug_layouts: HashMap::new(),
255 }
256 }
257
258 /// Creates a context borrowing `text_system` for this layout pass.
259 ///
260 /// The virtual viewport starts as `None`; with `devtools`, debug records
261 /// start empty.
262 ///
263 /// # Examples
264 ///
265 /// ```
266 /// use ailloli_ui_core::Scale;
267 /// use ailloli_ui_runtime::layout::LayoutContext;
268 /// use ailloli_ui_text::TextSystem;
269 ///
270 /// let mut text = TextSystem::new();
271 /// let ctx = LayoutContext::with_text_system(Scale::new(1.0), &mut text);
272 /// assert!(ctx.text_system.is_some());
273 /// ```
274 pub fn with_text_system(scale: Scale, text_system: &'a mut TextSystem) -> Self {
275 Self {
276 scale,
277 text_system: Some(text_system),
278 virtual_viewport: None,
279 layout_pass: LayoutPass::Commit,
280 measure_branches: Rc::new(RefCell::new(MeasureBranchState::default())),
281 current_measure_branch: None,
282 layout_attempt: None,
283 committed_layout_attempt: None,
284 reactive_layout_publisher: None,
285 reactive_layout_retry: None,
286 reactive_layout_abandon: None,
287 #[cfg(feature = "devtools")]
288 debug_layouts: HashMap::new(),
289 }
290 }
291
292 /// Returns the currently scoped content viewport.
293 ///
294 /// `None` means widgets should lay out without a virtualization hint.
295 ///
296 /// # Examples
297 ///
298 /// ```
299 /// use ailloli_ui_core::{Rect, Scale};
300 /// use ailloli_ui_runtime::layout::{LayoutCtx, VirtualViewport};
301 ///
302 /// let mut ctx = LayoutCtx::new(Scale::new(1.0));
303 /// ctx.replace_virtual_viewport(Some(VirtualViewport::new(
304 /// Rect::new(0.0, 50.0, 200.0, 100.0), 12.0,
305 /// )));
306 /// assert_eq!(ctx.virtual_viewport().unwrap().rect.y, 50.0);
307 /// ```
308 pub const fn virtual_viewport(&self) -> Option<VirtualViewport> {
309 self.virtual_viewport
310 }
311
312 /// Returns the authority of geometry produced by the current traversal.
313 ///
314 /// Widgets may always compute geometry, but must not persist effects based
315 /// on it while this returns [`LayoutPass::Measure`].
316 pub const fn layout_pass(&self) -> LayoutPass {
317 self.layout_pass
318 }
319
320 /// Returns the exact outer attempt owning the current layout callback.
321 ///
322 /// During `layout`, this identifies the active staging overlay. During
323 /// `layout_committed`, it identifies the validated overlay whose geometry
324 /// has just been published. Outside those callbacks it returns `None`.
325 #[doc(hidden)]
326 pub fn layout_attempt_token(&self) -> Option<LayoutAttemptToken> {
327 self.layout_attempt
328 .as_ref()
329 .map(LayoutAttempt::token)
330 .or(self.committed_layout_attempt)
331 }
332
333 /// Runs a descendant traversal under a sticky requested layout pass.
334 ///
335 /// Requesting [`LayoutPass::Commit`] beneath an existing measurement keeps
336 /// the descendant in [`LayoutPass::Measure`]. The previous pass is restored
337 /// after `layout` returns.
338 ///
339 /// # Examples
340 ///
341 /// ```
342 /// use ailloli_ui_core::Scale;
343 /// use ailloli_ui_runtime::layout::{LayoutCtx, LayoutPass};
344 ///
345 /// let mut ctx = LayoutCtx::new(Scale::new(1.0));
346 /// ctx.with_layout_pass(LayoutPass::Measure, |ctx| {
347 /// assert!(ctx.layout_pass().is_measure());
348 /// ctx.with_layout_pass(LayoutPass::Commit, |ctx| {
349 /// assert!(ctx.layout_pass().is_measure());
350 /// });
351 /// });
352 /// assert!(ctx.layout_pass().is_committed());
353 /// ```
354 pub fn with_layout_pass<R>(
355 &mut self,
356 requested: LayoutPass,
357 layout: impl FnOnce(&mut Self) -> R,
358 ) -> R {
359 let previous = self.layout_pass;
360 self.layout_pass = previous.descend(requested);
361 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| layout(self)));
362 self.layout_pass = previous;
363 match result {
364 Ok(result) => result,
365 Err(payload) => std::panic::resume_unwind(payload),
366 }
367 }
368
369 /// Runs an explicitly adoptable speculative measurement branch.
370 ///
371 /// The returned token abandons the branch by default. This hidden
372 /// cross-crate API lets layout widgets distinguish probes that contribute
373 /// to their final allocation from alternatives that were only evaluated.
374 #[doc(hidden)]
375 pub fn measure_branch<R>(
376 &mut self,
377 layout: impl FnOnce(&mut Self) -> R,
378 ) -> LayoutMeasurement<R> {
379 let parent_branch = self.current_measure_branch;
380 let branch_id = {
381 let mut state = self.measure_branches.borrow_mut();
382 state.next_id = state
383 .next_id
384 .checked_add(1)
385 .expect("layout measurement branch identity exhausted");
386 let branch_id = state.next_id;
387 state.parents.insert(branch_id, parent_branch);
388 branch_id
389 };
390 let previous_pass = self.layout_pass;
391 self.layout_pass = previous_pass.descend(LayoutPass::Measure);
392 self.current_measure_branch = Some(branch_id);
393 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| layout(self)));
394 self.current_measure_branch = parent_branch;
395 self.layout_pass = previous_pass;
396 match result {
397 Ok(value) => LayoutMeasurement {
398 value: Some(value),
399 branch_id,
400 state: self.measure_branches.clone(),
401 },
402 Err(payload) => std::panic::resume_unwind(payload),
403 }
404 }
405
406 /// Returns the explicit branch currently owning speculative work.
407 pub(crate) const fn current_measure_branch(&self) -> Option<u64> {
408 self.current_measure_branch
409 }
410
411 /// Returns whether a branch and every enclosing explicit branch were accepted.
412 pub(crate) fn measure_branch_is_adopted(&self, branch_id: u64) -> bool {
413 let state = self.measure_branches.borrow();
414 let mut branch = Some(branch_id);
415 while let Some(branch_id) = branch {
416 if !state.adopted.contains(&branch_id) {
417 return false;
418 }
419 let Some(parent) = state.parents.get(&branch_id) else {
420 return false;
421 };
422 branch = *parent;
423 }
424 true
425 }
426
427 /// Clears adoption records after the outer authoritative attempt finishes.
428 pub(crate) fn clear_measure_branch_adoptions(&mut self) {
429 let mut state = self.measure_branches.borrow_mut();
430 state.adopted.clear();
431 state.parents.clear();
432 }
433
434 /// Returns whether an outer layout call currently owns the staging overlay.
435 pub(crate) const fn has_layout_attempt(&self) -> bool {
436 self.layout_attempt.is_some()
437 }
438
439 /// Starts an empty staging overlay for one outer layout call.
440 pub(crate) fn begin_layout_attempt(&mut self) {
441 assert!(
442 self.layout_attempt.is_none(),
443 "nested layout attempts must reuse the active overlay"
444 );
445 self.clear_measure_branch_adoptions();
446 self.layout_attempt = Some(LayoutAttempt::new());
447 }
448
449 /// Borrows the active staging overlay.
450 pub(crate) fn layout_attempt(&self) -> &LayoutAttempt {
451 self.layout_attempt
452 .as_ref()
453 .expect("layout attempt is not active")
454 }
455
456 /// Mutably borrows the active staging overlay.
457 pub(crate) fn layout_attempt_mut(&mut self) -> &mut LayoutAttempt {
458 self.layout_attempt
459 .as_mut()
460 .expect("layout attempt is not active")
461 }
462
463 /// Removes the active overlay for validation, commit, or abandonment.
464 pub(crate) fn take_layout_attempt(&mut self) -> LayoutAttempt {
465 self.layout_attempt
466 .take()
467 .expect("layout attempt is not active")
468 }
469
470 /// Scopes one validated attempt identity to a post-layout hook.
471 pub(crate) fn with_committed_layout_attempt<R>(
472 &mut self,
473 token: Option<LayoutAttemptToken>,
474 callback: impl FnOnce(&mut Self) -> R,
475 ) -> R {
476 let previous = std::mem::replace(&mut self.committed_layout_attempt, token);
477 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| callback(self)));
478 self.committed_layout_attempt = previous;
479 match result {
480 Ok(result) => result,
481 Err(payload) => std::panic::resume_unwind(payload),
482 }
483 }
484
485 /// Installs runtime-owned dependency publication and retry callbacks.
486 pub(crate) fn set_reactive_layout_callbacks(
487 &mut self,
488 publisher: ReactiveLayoutPublisher,
489 retry: ReactiveLayoutRetry,
490 ) {
491 self.reactive_layout_publisher = Some(publisher);
492 self.reactive_layout_retry = Some(retry);
493 }
494
495 /// Installs the runtime-owned discarded-attempt diagnostic callback.
496 pub(crate) fn set_reactive_layout_abandon_callback(&mut self, abandon: ReactiveLayoutAbandon) {
497 self.reactive_layout_abandon = Some(abandon);
498 }
499
500 /// Publishes a prevalidated batch when this context belongs to a runtime.
501 pub(crate) fn publish_reactive_layout(
502 &self,
503 updates: &[ReactiveDependencyUpdate],
504 ) -> ReactiveDependencyBatchResult {
505 if let Some(publisher) = &self.reactive_layout_publisher {
506 publisher(updates)
507 } else {
508 ReactiveDependencyBatchResult::Accepted { renewed: false }
509 }
510 }
511
512 /// Schedules another targeted layout after a superseded attempt.
513 pub(crate) fn request_reactive_layout_retry(&self, element_id: ElementId) {
514 if let Some(retry) = &self.reactive_layout_retry {
515 retry(element_id);
516 }
517 }
518
519 /// Records one outer staging overlay discarded before publication.
520 pub(crate) fn record_reactive_layout_abandon(&self) {
521 if let Some(abandon) = &self.reactive_layout_abandon {
522 abandon();
523 }
524 }
525
526 /// Replaces the current content viewport and returns the previous value.
527 /// Layout containers use this to scope the hint to their child traversal.
528 /// Passing `None` clears the hint. The caller should restore the returned
529 /// value after laying out a scoped child.
530 ///
531 /// # Examples
532 ///
533 /// ```
534 /// use ailloli_ui_core::{Rect, Scale};
535 /// use ailloli_ui_runtime::layout::{LayoutCtx, VirtualViewport};
536 ///
537 /// let mut ctx = LayoutCtx::new(Scale::new(1.0));
538 /// let viewport = VirtualViewport::new(Rect::new(0.0, 0.0, 80.0, 60.0), 4.0);
539 /// assert!(ctx.replace_virtual_viewport(Some(viewport)).is_none());
540 /// assert_eq!(ctx.replace_virtual_viewport(None), Some(viewport));
541 /// ```
542 pub fn replace_virtual_viewport(
543 &mut self,
544 viewport: Option<VirtualViewport>,
545 ) -> Option<VirtualViewport> {
546 std::mem::replace(&mut self.virtual_viewport, viewport)
547 }
548
549 #[cfg(feature = "devtools")]
550 /// Records the latest layout and returns the stored debug snapshot.
551 ///
552 /// The first call for an element fixes `constraints_in`. Every call sets
553 /// `constraints_final` to `Some(constraints)` and replaces `layout_size`.
554 /// The map grows by unique element ID for the lifetime of this context.
555 ///
556 /// # Examples
557 ///
558 /// ```
559 /// use ailloli_ui_core::{Constraints, ElementId, Scale, Size};
560 /// use ailloli_ui_runtime::layout::LayoutCtx;
561 ///
562 /// let mut ctx = LayoutCtx::new(Scale::new(1.0));
563 /// let info = ctx.record_debug_layout(
564 /// ElementId(7), Constraints::tight(20.0, 10.0), Size::new(20.0, 10.0),
565 /// );
566 /// assert_eq!(info.layout_size, Size::new(20.0, 10.0));
567 /// assert!(info.constraints_final.is_some());
568 /// ```
569 pub fn record_debug_layout(
570 &mut self,
571 element_id: ElementId,
572 constraints: Constraints,
573 size: Size,
574 ) -> LayoutDebugInfo {
575 let entry = self
576 .debug_layouts
577 .entry(element_id)
578 .or_insert_with(|| LayoutDebugInfo {
579 constraints_in: constraints,
580 constraints_final: None,
581 layout_size: size,
582 });
583 entry.constraints_final = Some(constraints);
584 entry.layout_size = size;
585 entry.clone()
586 }
587}
588
589/// Handle used by widgets to lay out one retained direct child.
590///
591/// # Examples
592///
593/// ```
594/// use ailloli_ui_core::ElementId;
595/// use ailloli_ui_runtime::layout::LayoutChild;
596///
597/// let child = LayoutChild { element_id: ElementId(3) };
598/// assert_eq!(child.element_id, ElementId(3));
599/// ```
600pub struct LayoutChild {
601 /// Retained-tree identity delegated to [`LayoutEngine`].
602 pub element_id: ElementId,
603}
604
605/// Provides the operations defined for LayoutChild.
606impl LayoutChild {
607 /// Lays out this child with the supplied logical-pixel constraints.
608 ///
609 /// A stale or unknown `element_id` produces [`LayoutResult::zero`] and no
610 /// diagnostic cache event. Other behavior, including widget panics, is
611 /// delegated to [`LayoutEngine::layout_element`].
612 ///
613 /// # Examples
614 ///
615 /// ```
616 /// use ailloli_ui_core::{Constraints, ElementId, Scale, Size};
617 /// use ailloli_ui_runtime::element::ElementTree;
618 /// use ailloli_ui_runtime::layout::{LayoutChild, LayoutCtx, LayoutEngine};
619 ///
620 /// let mut tree = ElementTree::<()>::new();
621 /// let mut engine = LayoutEngine::new(&mut tree);
622 /// let mut ctx = LayoutCtx::new(Scale::new(1.0));
623 /// let mut child = LayoutChild { element_id: ElementId(99) };
624 /// assert_eq!(
625 /// child.layout(&mut engine, &mut ctx, Constraints::tight(10.0, 20.0)).size,
626 /// Size::default(),
627 /// );
628 /// ```
629 pub fn layout<A: 'static>(
630 &mut self,
631 engine: &mut LayoutEngine<'_, A>,
632 ctx: &mut LayoutCtx<'_>,
633 constraints: Constraints,
634 ) -> LayoutResult {
635 engine.layout_element(ctx, self.element_id, constraints)
636 }
637}