Chuyển tới nội dung chính

Bài 10 · Cooldown bằng bộ nhớ

Biến chết cùng lần chạy. Vài sự thật thì không nên: "tôi đã thu cái này rồi" phải sống sang lần chạy kế tiếp.

Bài toán

Một flow theo lịch chạy vài phút một lần, nhưng phần thưởng nó thu chỉ hồi lại mỗi chu kỳ một lần. Thu hai lần trong một chu kỳ thì lần thứ hai tốt nhất là công cốc, tệ nhất là có hại.

Flow cần hỏi, ở đầu mỗi lần chạy: một lần chạy gần đây đã làm việc này chưa? — và câu trả lời phải sống bên ngoài bất kỳ lần chạy đơn lẻ nào.

Bạn sẽ học được

  • Runtime Memory (Bộ nhớ tạm): các mục có tên, trường dữ liệu, phạm vi (scope) và TTL.
  • Memory Match (So khớp bộ nhớ): đối chiếu một ứng viên với mọi mục đã nhớ.
  • Khuôn mẫu chốt-chặn-cooldown: khớp trước, nhớ sau, rồi mới làm.
  • Vì sao TTL ăn đứt dọn dẹp thủ công.

Trước khi bắt đầu

  • Hoàn thành Bài 09. Cổng OCR bật (ta đọc đồng hồ chu kỳ).

Flow

Flow Bài 10 trong trình soạn thảo

Badge đánh số node theo thứ tự đọc, khớp phần Dựng flow. Memory Match ở trên chính là trọng tâm — nó quyết định phần việc bên dưới có chạy hay không, nên cooldown nằm gọn trong một node thay vì rải khắp flow.

Dựng flow

  1. Flow mới; khai báo cycle_minutes (number?).
  2. Read Text 1. Read the cycle timer trên ô Next Cycle, kiểu Time, phút → cycle_minutes.
  3. Memory Match 2. Already collected this cycle?:
    • Memory: pa_collected_cycles
    • Expression: {{item.cycle}} == {{cycle_minutes}}
  4. Runtime Memory 3. Remember this cycle (10 min TTL):
    • Operation remember, memory pa_collected_cycles, scope persist
    • Một trường: cycle = {{cycle_minutes}}
    • TTL 600 giây
  5. Click vào Collect, và ba Stop như sơ đồ (skipped là một success — từ chối làm trùng việc là hành vi đúng).
  6. Nối: khớp trước, rồi nhớ, rồi làm. Save, Auto Layout.

Hai node bộ nhớ phối hợp thế nào

Runtime Memory ghi; Memory Match đọc. Một mục là bản ghi tí hon — ở đây {cycle: 20} — lưu dưới một tên bộ nhớ, trong một scope, với một vòng đời:

MảnhỞ đâyVì sao
Tên pa_collected_cyclesNhãn chung cả hai node dùngGõ sai ở một bên = chốt chặn không bao giờ khớp
Trường cycleThứ định danh "việc này"Được khớp qua {{item.cycle}}
Scope persistSống qua khi lần chạy kết thúc khi app khởi động lạiChính là mấu chốt — scope run sẽ quên ngay tắp lự
TTL 600 sMục tự xóa mìnhCooldown tự hết hạn: không flow dọn dẹp, không danh sách ôi thiu

Memory Match định giá biểu thức một lần cho mỗi mục còn sống, với mục đang xét lộ ra dưới dạng {{item.…}}. Bất kỳ mục nào thỏa → matched. Mục hết hạn đơn giản là biến mất — TTL là thứ biến "nhớ mãi mãi" thành "nhớ đúng một chu kỳ".

Thứ tự là tất cả: khớp → nhớ → làm. Nhớ trước khi khớp thì mọi lần chạy đều khớp chính mục tươi rói của mình — flow bỏ qua vĩnh viễn. Làm trước khi nhớ thì một cú sập giữa hai bước sẽ thu hai lần. Thứ tự an toàn là hỏi trước, ghi nhận sau, làm việc cuối cùng.

Dung sai ngoài đời thực

Ở đây so bằng trên phút là đủ. Tọa độ thật từ OCR xê dịch cỡ pixel — vì thế Memory Match nhận nguyên một biểu thức: abs({{current_x}} - {{item.x}}) <= 1 khớp gần, không phải chính xác. Chốt chặn của capstone dùng người anh em giản dị của đúng mẹo này.

Chạy thử

  1. Lần đầu: đồng hồ đọc 04:20 → không mục nào khớp → nhớ {cycle: 20} → Collect → success · Collected this cycle.
  2. Chạy lại ngay: cùng kết quả đọc → matchedsuccess · Already collected this cycle. Không cú chạm nào xảy ra.
  3. Đợi mười phút (hoặc đổi đồng hồ: dashboard.html?timer=07:15) rồi chạy: mục cũ đã hết hạn (hoặc không khớp) → thu lại.

Lần chạy 2 chính là bài học: một lần chạy khác, nhiều phút sau, biết lần chạy 1 đã làm gì.

Best practices

  • Khớp trước, nhớ sau, làm cuối.
  • TTL = cooldown thật, không phải "dài cho chắc" — dài quá sẽ bỏ qua việc chính đáng.
  • Scope persist cho chốt chặn theo máy. Chỉ dùng shared khi mọi instance phải tôn trọng một danh sách chung — khi đó chúng đè lên tuyên bố của nhau là chủ đích.
  • Đặt tên bộ nhớ cụ thểpa_collected_cycles, không phải data.
  • Bỏ qua là thành công. Nối nó như một thành công.

Lỗi thường gặp

LỗiHậu quảCách sửa
Đặt Remember trước MatchKhớp chính mục của mình; bỏ qua vĩnh viễnKhớp trước
Tên bộ nhớ lệch giữa hai nodeChốt chặn không bao giờ khớpCùng một chuỗi ở cả hai phía
Tên trường ≠ tham chiếu {{item.…}}Không bao giờ khớpcycle{{item.cycle}}
Không TTLMục chất đống; việc bị bỏ qua rất lâu sau cooldownLuôn đặt TTL
Scope runQuên khi lần chạy kết thúc — chốt chặn vô dụng giữa các lần chạypersist

Khắc phục sự cố

Không bao giờ bỏ qua, kể cả chạy sát nhau. Kiểm tra tên bộ nhớ ở cả hai node, rồi tên trường/item, rồi xem scope của phép ghi có phải run không.

Lúc nào cũng bỏ qua. Thứ tự node — remember nằm trên match. Nối lại.

Bỏ qua sau khi khởi động lại app, không ngờ tới. Đó là persist đang làm đúng việc: TTL là thời gian tuyệt đối và sống qua khởi động lại. Nếu bạn muốn "chỉ trong phiên chạy app này", scope session mới là công cụ.

Tóm tắt

  • Runtime Memory + Memory Match = sự thật sống lâu hơn lần chạy, với vòng đời tự hết hạn.
  • Khuôn mẫu chốt chặn là một thứ tự: khớp → nhớ → làm.
  • Scope chọn ai chia sẻ bộ nhớ; TTL chọn nó ràng buộc bao lâu.

Tiếp theo

Bạn đã nắm mọi kỹ năng đơn lẻ: hành động, xác minh, đọc, đếm, retry, khôi phục, ghi nhớ. Bài 11 · Xây bằng Sub Flow là nơi các flow bắt đầu gọi lẫn nhau — và các bài học của bạn theo đúng nghĩa đen trở thành một thư viện.

Xem thêm: Runtime Memory · Memory Match · Biến và bộ nhớ