#ChronoGrapher - One Unified Scheduler, Unlimited Power

1 messages · Page 6 of 1

placid drum
#

oh i see then its really good feature

#

i am wrting doc for dependancy if i possible i'll make pr today.

fervent lark
#

oki dokie

#

im gonna write gudebook docs

fervent lark
#

finished the dependencies chapter

placid drum
#

Are you writing DOC?

fervent lark
#

?

fervent lark
placid drum
#

Docs

#

Documentations

fervent lark
#

like which docs

#

the guidebook or API docs?

placid drum
#

Api docs

#

In code

fervent lark
#

no i don't write API docs

placid drum
#

Oh okay

fervent lark
#

bois

#

we have to keep moving

#

we should stop lazing around (even if today i had a tiresome day)

#

though il be taking a break, a bit of the burnout is catching up

formal sedge
#

What does the project lacks for the alpha ?

fervent lark
#

wait hold on imma bunch it up

fervent lark
# formal sedge What does the project lacks for the alpha ?
  • Fully-made Proc Macros
  • Complete Guidebook + Guidelines (easily ~30-50k words)
  • Complete API docs
  • All Unit Tests (with every scenario)
  • CI/CD pipeline
  • The website. (landing page along with some other pages)
  • The benchmarks
  • A couple of optimizations for the crate to make it run faster
  • Fixing the memory leakage bug (though i think we can leave it as is tbh, it won't be that bad since its alpha)
#

and thats scratching the surface

#

then there will be:

  • Bindings for each programming language (repeat the API & Guidebook docs, benchmarks, optimizations and unit tests steps per binding though not as severe as now)
  • Local / Single-node persistence
  • TaskHandles
  • Abstracting the async runtime
  • The Simulacrum
  • More optimizations
    ...etc.
#

@formal sedge i think you now understand why we need a bit more expertises

#

than just unit tests and api docs

fervent lark
#

at best we may even finish September,

#

1 YEAR

#

on one project

#

on the core of all things without an alpha release

formal sedge
#

I mean

#

your alpha looks really more as a beta

#

I know I am too optimistic, but things like Website CI/CD and full test coverage is long

#

Think that the best thing is to first focus on the core of the core and then integrates things like CI and website when core is in an usable state

#

Like proc macros is basically the main use of the core API

fervent lark
fervent lark
fervent lark
#

i want to avoid announcing beta as the BSL license would need to promote to MIT

#

for now il avoid calling it beta and call it alpha

#

the reason i do wan to keep BSL is its a temporary license, the idea is to discourage competition from just forking it and expanding upon

formal sedge
fervent lark
#

what the actual fuck?

#

im like flasterbanged, what?

#

im so confused

#

what does a nutrational company have to do with my product first, second is this advertisment, is this a job like huh?

formal sedge
#

Why is website has an rust endpoint😂

fervent lark
#

wouldn't reccomend going in the website

fervent lark
#

their account seems to have been created 4 years ago but recently active in March of 2026

#

somewhere today they've customized their profile?

placid drum
#

@fervent lark i am busy doing assignment and the other work i cant take out time till end of june (its exam) unless its weekend

fervent lark
#

damn...

#

this sucks

#

samy isn't working frequently

#

and we're like 3 peps

#

mostly doing the work

placid drum
#

i'll try my best on weekend

#

well i cant stop exam

fervent lark
#

i mean after all you're contributing

#

not like being payed for it to be more serious

placid drum
#

no obligation

fervent lark
#

and you're helping for the most part

placid drum
#

i wanna start solving issue

#

but the problem is my skill are not good enough for now

fervent lark
#

i finished an intermediate chapter

#

for schedules

#

for the guidebook in order to be finished il have to:

  • Make the "Introspecting Workfows" chapter series explaining TaskHooks (should be around 5 chapters explaining TaskHooks)
  • Make the "ChronoGrapher's Otherside" chapter series explaining the Scheduler side (i'd imagine this will be by far the biggest at a whopping 10 chapters, somewhere there)
  • Make a chapter series diving into the Base API (type-level, without the proc-macros that simplify lots of things, should be ~5 chapters)
  • Finish the contribution guidelines
  • Make a FAQ chapter for common questions

And i think its done

#

for the entire guidebook series its ~31-35k words in length or about 31-35 chapters

#

plus the contribution guidelines its 40-50k words

fervent lark
#

imma pause the guidebook a bit

#

tbh i really want to do the TaskHandle based approach

placid drum
#

@fervent lark did you update guidebook ?

#

on github ?

fervent lark
#

for the rest like TaskHooks and so on, well not

placid drum
#

where can i read ? website ?

fervent lark
#

on website/

placid drum
#

ahh i see there is new tasks to workflow

#

ig it was old well anyway there is dependancy chapter i am looking for

fervent lark
#

okie

#

ye it has one dependency chapter

placid drum
#

@fervent lark is FrameDependency depends of schedular ?

fervent lark
#

?

#

wdym

placid drum
#

is scheduler checks is resolve before dispatching a frame?

#

related to FrameDependency

#

or else just tell me use of frame dependency trait

#

is it same as standard dependency in guidebook ?

#

@fervent lark can i write this

/// A [`FrameDependency`] is base triat for any dependency, it has a single prerequisite
/// that must be satisfied before a task frame is allowed to run. 

fervent lark
fervent lark
#

second

#

im not sure about "single prerequisite", keep in mind there are logical dependencies (OR, AND, NOT, XOR)

#

which im not sure if they count as "single"

fervent lark
#

man...

#

i realized

#

we haven't even touched the main landing page

#

for ChronoGrapher

fervent lark
#

man i feel like taking a break

placid drum
fervent lark
#

happy new month

#

@jolly frost @placid drum how is progress going?

#

ngl its quite damming to realize we entered May and the project is still not delivered in its first alpha stage ferrisAware

fervent lark
#

il have to geet back to speed things up

fervent lark
#

no way

#

-# 60 subscribe special video is coming soon /j

#

enough though with the jokes

#

its time for serious effort

#

🤔 for the TaskHandle i have to think a different approach

fervent lark
#

back to the drawing board, its just too difficultt o make TaskHandle work with its various lifetime issues and other complexities

#

though its hared to think a different solution

#

man this issue is present for months

fervent lark
#

man i feel empty

#

like i litterally have no idea on what to do

#

the problems are complicated af

#

and im the wost guy to explain these problems to some outsider

fervent lark
#

@jolly frost you good man?

fervent lark
#

since they use the crate chronographer and well we are in chronographer_base

#

wait

#

it can be fully avoided

#

i just added chronographer as a dev dependency

fervent lark
#

im slowly fixing the doctests

#

il have to also rework on them since im not really sure if they are strict enough

#

so far i got the errors down to 3 failures (2 of which come from the cron macro not being materialized)

#

i am looking reworking tests such as this:

/// \`\`\`
/// use std::num::NonZeroU32;
/// use std::time::Duration;
/// use chronographer::task::TaskFrameBuilder;
/// # use chronographer::task::{TaskFrame, TaskFrameContext, FallbackTaskFrame, TimeoutTaskFrame, RetriableTaskFrame};
/// # use chronographer::errors::TimeoutTaskFrameError;
/// # use async_trait::async_trait;
/// # use std::any::{Any, TypeId};
/// # struct MyTaskFrame;
/// #
/// # impl TaskFrame for MyTaskFrame {
/// #     type Error = String;
/// #     type Args = ();
/// #
/// #     async fn execute(&self, _ctx: &TaskFrameContext, _args: &Self::Args) -> Result<(), Self::Error> {
/// #         Ok(())
/// #     }
/// # }
///
/// # struct BackupFrame;
/// #
/// # impl TaskFrame for BackupFrame {
/// #     type Error = String;
/// #     type Args = TimeoutTaskFrameError<String>;
/// #
/// #     async fn execute(&self, _ctx: &TaskFrameContext, _args: &Self::Args) -> Result<(), Self::Error> {
/// #         Ok(())
/// #     }
/// # }
/// // `MyTaskFrame` and `BackupFrame` are two impls that implement `TaskFrame`.
///
/// const DELAY_PER_RETRY: Duration = Duration::from_secs(1);
/// # type WorkflowType = FallbackTaskFrame<TimeoutTaskFrame<RetriableTaskFrame<MyTaskFrame>>, BackupFrame>;
/// # type WorkflowPermut1 = FallbackTaskFrame<RetriableTaskFrame<TimeoutTaskFrame<MyTaskFrame>>, BackupFrame>;
///
/// let composed = TaskFrameBuilder::new(MyTaskFrame)
///     .with_retry(NonZeroU32::new(3).unwrap(), DELAY_PER_RETRY) // Failure? Retry 3 times with 1s delay
///     .with_timeout(Duration::from_secs(30)) // Exceeded 30 seconds? terminate and error out with timeout
///     .with_fallback(BackupFrame) // Received a timeout or another error? Run "BackupFrame"
///     .build();
///
/// # assert_eq!(composed.type_id(), TypeId::of::<WorkflowType>());
/// # assert_ne!(composed.type_id(), TypeId::of::<WorkflowPermut1>(), "Unexpected matching workflow types");
/// \`\`\`
#

bc rn its cumbersome, i mean its shows general use case

#

but no strict checking

#

actually wait

#

il just simplify

fervent lark
#

I am rethinking to also rework the dependency system

#

more specifically avoid the use of ArcAtomicBool> everywhere external to the users

fervent lark
#

for the dependency system, the more i think about it, its useless for it to be a trait

#

kind of

#

but its faster to use an enum instead

#

without sacraficing extensibility

#
let simple_dependency1 = Dependency::all(&[my_dependency1, my_dependency2, my_dependency3]);
let simple_dependency2 = Dependency::any(&[my_dependency1, my_dependency2, my_dependency3]);
let simple_dependency3 = Dependency::not(&my_dependency1);

let complex_dependency = Dependency::builder()
  .task_runs(5) // Resolve when Task runs for 5 times
  .task_successes(3) // Resolve when Task runs successfully for 3 times
  .task_failures(2) // Resolve when Task runs successfully for 2 times
  .consective_task_successes(3) // Resolve when Task runs successfully for 3 times CONSECTIVELY
  .consective_task_failures(2) // Resolve when Task runs successfully for 2 times CONSECTIVELY
  .timeout(Duration::from_secs(3)) // Resolve after a certain time passes
  .dependency(simple_dependency1) // Custom dependencies
  .all(); // All sub-dependencies must resolve, there is "any" as well for either one and "not" (requires ONE dependency)

complex_dependency.disable(); // Disables the entire dependency
complex_dependency.enable(); // Enables the entire dependency
complex_dependency.reset(); // Resets the state of the dependency (if it supports it)
fervent lark
#

honestly idk, there might be a better idea to present these dependencies

#

while retaining expression

#

actually operator overloading of BitAnd, BitOr and Not will help

fervent lark
#

the interface is tbh better with operator overloading

formal sedge
#

There is operator overloading in rust ?

fervent lark
#

i am rewriting some of the unit testing for the dependency system

#

due to the recent changes

#

im kinda bored af to do but eh

#

whatever

fervent lark
#

ngl we are going too slow

#

than i would like

fervent lark
#

honestly idk but life's kinda hard, for some reason i feel demotivated to continue the progress i was going with on the project

#

my contribution rate is in an all time low

fervent lark
#

honestly i feel like its best to stick to the vision of delivering a basic alpha version, limit the scope drastically

#

we need see completion to feel the dopamine hit to continue

#

honestly i should not care for the project personally for like 2 weeks

#

imma take a break, distracting myself with some smaller project for the dopamine hit then move back

fervent lark
#

though one thing im kinda wondering is what to do 🤔

#

i want something more "immediately rewarding", something UI based like an application just have no idea what to do

fervent lark
#

ummmm...

#

im sorry? xd

fervent lark
#

I am planning to make my own excaclidraw

#

rn excaclidraw is not too flexible, though its simpler and faster to use over a vector-based editor such as affinity for these diagrams

#

the opposite holds true for vector-based editors as well

fervent lark
#

hm?

#

who pinged me?

fervent lark
fervent lark
#

i think its time to take care of the website

#

my conflicting thoughts are to distract myself as a break with some other smaller side project, and then work on ChronoGrapher vs the dreadful delay of ChronoGrapher's first release

#

its gonna be a huge pain in the ass (and it already is and was) ferrisAware

#

though i don't have a clear idea of to present stuff

#

i kinda have the style just right, though i wanna make it more technological

placid drum
fervent lark
placid drum
fervent lark
#

lol

placid drum
#

so are you working on alpha or excaclidraw ?

fervent lark
#

i kinda changed my idea and i do want to finish this project's core real soon

fervent lark
#

also @placid drum when you planning to return to work?

#

this weekend?

placid drum
#

I am reading new blogs and lot of stuff

fervent lark
#

tbh you will do the API docs like you always did

placid drum
fervent lark
#

hmmm

#

you could try to tackle the guidebook docs

#

how about that?

placid drum
#

tbh i really dont understand whole system

fervent lark
#

you don't need to understand the whole thing

#

you just learn stuff when you need to do something that requires this knowledge

placid drum
#

whats task ?

fervent lark
#

you will have to read the Guidebook Guidelines

#

to understand how to manage the system

#

plus you have to know TaskHooks in great detail

#

after that you will work on the Introspecting Workflows chapter series

#

feel free to look on previous chapters for the structure and way of writing

placid drum
#

okay i will understand and ask you about it

fervent lark
placid drum
#

at least learning english properly.

fervent lark
#

ye

#

the next chapter should include the following:

  • How to create your own TaskHookEvents
  • How to emit your own events in places
  • What is the Hook-To-Hook Communication pattern
#

and i think it should be it

#

the chapter title should be something like Custom Events & Communication

placid drum
#

hmm got it.

fervent lark
#

i reccomend taking a stroll on the previous chapters, like read them and critique them

placid drum
fervent lark
#

explain what you liked versus hated

#

in detail

#

point out mistakes... etc.

placid drum
#

ya sure

fervent lark
#

btw update your project

#

as i pushed some new commits

placid drum
#

ya i'll finish things in my hand and start working on it.

fervent lark
#

ok

fervent lark
#

god its just dreadful to look the calendar, realize its May 16 and the project's core is like 60% finished with the other 40% being a lot of stuff to do (docs, unit tests, CI/CD pipeline... etc.)

placid drum
#

why 1st example not working from installation and theory ??

fervent lark
#

thats why

#

which aren't implemented rn

#

like ye there is the every! macro, but no like attribute macros which it does use

placid drum
#

need to understand what is pending and completed

fervent lark
#

what

#

im confused

placid drum
#

i am talking about the macros part need to understand which are completed and which are pending

fervent lark
#

you will need to rely on the base API

#

for instance

placid drum
#

oh i see now implementing macros would be fun.

fervent lark
#

its not as easy as you think it is

#

trust me

placid drum
#

i know if it was easy you have already finished it.

fervent lark
#

ye

#

tbh you could try

#

i'd reccomend start with the "basic" macros

#

cron! for instance

#

then work your way up, so you can understand the model better

placid drum
#

okay but before i'll finish current chapter i am writing.

fervent lark
#

btw, this is what the example using macros:

use chronographer::prelude::*;
use thiserror::Error;

// Define the errors our application can throw (for now empty)
#[derive(Error, Debug)]
pub enum MyErrors {}

// Define our singleton Task to run
#[task(schedule = interval(2s))]
async fn MyTask(ctx: &TaskFrameContext) -> Result<(), MyErrors> {
    println!("Hello ChronoGrapher!");
    Ok(())
}

#[chronographer::main]
async fn main(scheduler: DefaultScheduler<MyErrors>) {
    let task_inst = MyTask::instance(); // Tasks defined like above are singleton
    let _ = scheduler.schedule(task_inst).await;
}

This translates to base API:

use chronographer::prelude::*;
use thiserror::Error;

pub struct MyTaskFrame;
impl TaskFrame for MyTaskFrame {
   type Args = ();
   type Error = MyErrors;

   async fn execute(&self, ctx: &TaskFrameContext, &args: Self::Args) -> Result<(), MyErrors> {
         println!("Hello ChronoGrapher!");
         Ok(())
   }
}

// Define the errors our application can throw (for now empty)
#[derive(Error, Debug)]
pub enum MyErrors {}

#[tokio::main]
async fn main() {
   let scheduler = LiveScheduler::<DefaultSchedulerConfig>::default();

   let task = Task::new(
       MyTaskFrame,
       TaskScheduleInterval::from_secs(2)
   );

   let _ = scheduler.schedule(task);
   scheduler.start().await;
   loop {} // It will have "CTRL + C" handling, not just a loop. This is for simplicity
}
#

you can see why macros are more appealing than writing in raw Base API

#

also @placid drum what kind of chapter are you writing exactly?

placid drum
#

ya macro should be there

fervent lark
#

the continuation of the TaskHooks? Or something else?

placid drum
#

hook to hook comunication

fervent lark
#

ah ok

fervent lark
#

like an estimate

#

its highly reccomended to stay in the bound of 1k words each chapter, since its a nice middleground between long enough to cover the details but short enough for the user to sit down, read it fully and feel productive

#

unless you got a good reason (rare) to do so, such as the topic itself cannot be seperated to smaller chapters, or leave out advanced concepts out of the window for future chapters to cover... etc.

placid drum
fervent lark
#

okie dokie

placid drum
#

@fervent lark i was reading the prv chapter's to understand how everything works, found out that -
The second phase or known as Registration Phase involves (do you think its correct statement because i dont think so for me ig it should be - its second way rather than phase )

#

because it dosn't continue with first phase

#

there is no first phase my bad

fervent lark
placid drum
#

where ?

#

There are 2 phases to use the TaskHooks, the former is the Implementation / Definition Phase while the latter is Registration Phase, in the first,

#

gotch

fervent lark
#

ye

#

the definition phase is where you define the TaskHook

placid drum
#

but isnt this is same thing in done different way ?

fervent lark
#

when you make a TaskHook, its just a container, a definition

#

it has its data

#

it has its events to listen to

placid drum
#

f* i got it now

fervent lark
#

but where would it act upon? The answer is on nothing

#

so you need a Task to mount it / attach it onto

#

thats why the registration phase is needed, so it knows where it is attached to

placid drum
#

phase one create just definition, and then in next phase we attach to task ?

fervent lark
#

you can attach to multiple Tasks, not just one

#

more specifically, not only you attach it to a Task, you have to attach it to a specific event

#

for example

#

if you attach it on the event OnTaskStart, well it would trigger the code for when said Task starts

#

OnTaskEnd for when a Task ends, and so on for other events

placid drum
#

cool

fervent lark
#

you don't have to attach the same instance to multiple events, but at least you have to attach it to one

placid drum
#

ya understand now

fervent lark
#

its basically the event listening / observer pattern, just more typed

#

as opposed to using strings everywhere

#

one of the problems with this system btw is you have to register manually to every event. Suppose an example like this

#
impl<E: TaskHookEvent> TaskHook<E> for MyTaskHook {...}
#

and yes you can use generics to generalize side-efffects to a category of events

#

this code no matter which event it is attached to, will run the same code (plus it has no idea which event it is)

#

if you need to narrow, well:

impl<E: TaskLifecycleEvents> TaskHook<E> for MyTaskHook {...}
#

this runs the same code for OnTaskStart and OnTaskEnd

#

these are called TaskHook Event Groups (THEGs for short)

placid drum
#

no kidding its really cool and complex does people really use this kinda things ?

fervent lark
#

on the surface, TaskHooks are basic

placid drum
fervent lark
#

be specific

#

wdym

placid drum
#

i am not able to think where can be use this kinda complex workflow

placid drum
fervent lark
#

say you want to add logging right? Every distributed service, hell even a basic server needs logging for monitoring

placid drum
fervent lark
#

how do you execute your own code / side-effects when some Task runs at the start, or finishes?

#

you need to well, execute your code to log stuff

#

thats one of the uses for TaskHooks

#

and generally everything that requires listening to events happening in a system, and reacting is bound by this example

placid drum
#

i know the importance of taskhook

fervent lark
#

another use i can think off the top of my head is suppose a special kind of Task

#

right now our system treats a Task as a static struct, you have only the TaskFrame, the TaskSchedule and the storage of TaskHooks

placid drum
#

but i am talking about the where in industries, workflow orchestration is used like, they only thing i used is cron

fervent lark
#

cron doesn't scale once your app is discord's level of complexity

#

or similar complexity

#

you add distributed systems right? Well, cron doesn't support distributed systems so you need to work around that somehow

#

then you will need observability, so you'd have to integrate with your library of choice (Prometheus, Grafaana, Datadog... etc.), well there is more work to getting this right

#

and before you know it, you have made a duct-tape messy workflow orchestrator

placid drum
#

damn crazy

#

never thought this way

fervent lark
#

only once you introduce enough complexity, then an orchestrator becomes viable / appealing

#

like have you ever used http://temporal.io for basic stuff?

#

the answer is hell no

#

its too complex for what you need, but once you scale out, then you will start using temporal more

placid drum
#

never get chance to work on this scale

fervent lark
# placid drum no

ChronoGrapher takes it a step further, instead of having to switch entirely your stack and rewriting countless of duct tape code (not to mention the entire time wasted writing it in the first place)

#

what if you already start at a small scale

#

ChronoGrapher is meant to transition smoothly between various scales from smallest to highest, temporal remains a fixed toolkit, ChronoGrapher adapts

#

thats the philosophy

#

i hope you get what i mean

placid drum
#

damn need to learn more

fervent lark
placid drum
#

title: Custom Events & Communication
description: Learn how to define your own TaskHookEvents, emit them anywhere in your code and use Hook-To-Hook Communication

In the previous chapter we learn about the two phases of TaskHooks Implementation and Registration where we
saw how to carry state inside a hook, use singletons to share that state across events and Tasks, and learned the
Stateful Container pattern for hooks that hold data without listening to any event.

In this chapter we going to define our own TaskHookEvent, emit them. also we are going to understand how Hook-To-Hook Communication
works.

Defining A Custom TaskHookEvent

To define our custom TaskHookEvent we have to satisfies the TaskHookEvent trait. the trait has associated type Payload,
which carries the data that travel alongside the event when it is emitted:

<RenderProgrammingLanguageBased target_name={"Rust"}>

use chronographer::prelude::TaskHookEvent;

#[derive(Default)]
pub struct OnRowsFetched;

impl TaskHookEvent for OnRowsFetched {
    type Payload<'a> = usize where Self: 'a;
}

</RenderProgrammingLanguageBased>

Now let's understand what we wrote. OnRowsFetched is a marker struct, it carries no data itself. The payload, usize, is the
number of rows returned in batch and travels with the event when it is emitted. we will have () valid payload type when event carries no
data at all.

#

how's this so far

fervent lark
#
In the previous chapter we learn about the two phases of TaskHooks Implementation and Registration where we 
saw how to carry state inside a hook, use singletons to share that state across events and Tasks, and learned the
Stateful Container pattern for hooks that hold data without listening to any event.

I don't think you needed this long ass description to explain what we've done on the previous chapter

#

i reccomend shrinking it, you can omit some information such as the Stateful Container Pattern

placid drum
#

okay

fervent lark
#

we going -> we are going

#

also we are going to understand how Hook-To-Hook Communication works. feels abrupt / awkward, try to chain it in one sentence, not to mention also should have been Also but still

#

satisfies -> satisfy

#

i am noticing you are too used to API docs

#

this thing is more narrative than technical manual fashion like API docs

#

you are focusing on syntax

placid drum
#

hmm need to change the approach

fervent lark
#
To define our custom TaskHookEvent we have to satisfies the TaskHookEvent trait. the trait has associated type Payload,
which carries the data that travel alongside the event when it is emitted:

This also sounds like it has no energy, not to say you have to make it childish

#

but it feels like you are bored to present the topic

placid drum
fervent lark
#

ik you ain't bored

placid drum
#

no

#

its not

fervent lark
#

but im viewing it in the eyes of the reader

placid drum
#

ya i am good with critics

#

forces me to push forward

#

english doesn't come naturally from me its my 3rd language so you know kinda its difficult to explain things, well anyway.

fervent lark
#

3rd

#

kinda impressive

fervent lark
#

to showcase you what i would do to boost this

placid drum
fervent lark
#

I'd rewrite it in this kind of fashion:

title: Custom Events & Communication
description: Learn how to define and emit your own TaskHookEvents

In the previous chapter we learnt about the two phases of TaskHooks, the Implementation Phase and Registration Phase, we saw how to subscribe to events, sharing TaskHook instances between events and Tasks and even adding data for our TaskHook to hold.

In this chapter we will focus on the event part of TaskHooks, how to create your very own events, how to emit them in your own places and understand Hook-To-Hook communication.

Defining A Custom TaskHookEvent

Defining a custom event for our TaskHooks to listen to is as simple as:

<RenderProgrammingLanguageBased target_name={"Rust"}>

rust title="src/events.rs" lineNumbers
use chronographer::prelude::TaskHookEvent;

#[derive(Default)]
pub struct MyTaskHookEvent;

impl TaskHookEvent for MyTaskHookEvent {
    type Payload<'a> = () where Self: 'a;
}

In our example above, we have defined MyTaskHookEvent as a TaskHookEvent, by creating a typical struct and implementing the TaskHookEvent trait. This trait requires the Default trait as such, we derived it.

In our previous chapter you may have noticed a payload parameter for our TaskHooks. This payload parameter is the same as the associated type Payload<'a>, in our case we defined it to () which means it has no data to carry.
</RenderProgrammingLanguageBased>

Payloads are used to hand out extra information onto the TaskHook about the event, they are a transport layer between the cause of the event and the listeners (the TaskHooks). This data can be anything from a simple number to an array of strings to a handler... etc.

#

don't blindly copy paste this, do examine why i wrote it in this way

#

also:

In our example above, we have defined MyTaskHookEvent as a TaskHookEvent, by creating a typical struct and implementing the TaskHookEvent trait. This trait requires the Default trait as such, we derived it.

In our previous chapter you may have noticed a payload parameter for our TaskHooks. This payload parameter is the same as the associated type Payload<'a>, in our case we defined it to () which means it has no data to carry.

This is on purpose inside the RenderProgrammingLanguageBased, as it discusses rust specifics not found elsewhere in other programming languages, as such it deserves to be inside there

#

try to be general about features, don't go to syntax detail and if you do, surround it with a RenderProgrammingLanguageBased. While Rust is the only language for now, the idea is to prevent users from Python having to conceptualize / understand any Rust details

#

the guidebook adapts per programming language

#

also keep things incremental

#

increase complexity slowly, you may notice the Payload wasn't really explained and given an example for an integer value, i would do it later once the user has been accustomed to what the payload is

placid drum
#

Yes need to compare with mine understand what should be ideal way and mistakes I did

fervent lark
fervent lark
#

i edited the message with this in mind

placid drum
fervent lark
#

like analyze the structure, presentation... etc.

placid drum
#

Hmm ya I'll do that.

#

Thank you for the help

fervent lark
#

np

fervent lark
#

@placid drum

#

howz the chapter going?

placid drum
#

i will be free around in 1hr then start working on it, i try to raise pr.

fervent lark
#

okie dokie

placid drum
#

@fervent lark we can do this right ?

#
#[async_trait]
impl TaskHook<OnRowsFetched> for FetchMetricsHook {
    async fn on_event(&self, \_ctx: &TaskHookContext, payload: &<OnRowsFetched as TaskHookEvent>::Payload<'*>) {
        let total = self.total_rows.fetch_add(*payload, Ordering::Relaxed) + *payload;
        println!("Batch: {} rows | Running total: {total}", payload);
    }
}

fervent lark
#

yup

#

also

#

OnRowsFetched isn't a good example of an event

#

thats like database stuff, they aren't really known for events and stuff

#

try to write the examples in a way which users would encounter in real code, not too realistic with its complexity but not too hypothetical / academic

#

also \_ctx: &TaskHookContext?

#

like just write _ctx: &TaskHookContext

placid drum
#

okay

placid drum
#

@fervent lark created pr

fervent lark
#

ok

placid drum
#

check please give me feedback on this thanks.

fervent lark
#

first of

fervent lark
#

like use a comma

#

again you seem to focus on syntax

#

nvm

#

my bad

placid drum
#

well i didnt use ai well i use for example change

fervent lark
# placid drum well i didnt use ai well i use for example change

semi-good you didn't use AI, would reccomend using AI though as like a "polish" tool, the idea is to iteratively ask it gaps to identify then it gives you those gaps, which you filter these out depending on if they are bullshit or genuine feedback, then you fix them on your own and rence and repeat

#

but you NEVER blindly follow the AI

#

The `ctx` argument provided to every task is a `TaskFrameContext also this is filler

#

the user knows about the context object, no need to re-explain it

placid drum
#

okay so we are skiping things happen in prv chapter ?

fervent lark
#

you can just mention, "TaskFrameContext contains various methods for us to use, but in our case we will be using emit"

fervent lark
#

no need to constantly re-explain

#

unless its summary or anything like that, but in this case its not

placid drum
#

make sense i was under the impresion that we have to just ignore the basic stuff

fervent lark
#

also the example you provided while semi-good

#

with the notifications

#

i suggest we can take it a step further

#

rn its not working, why don't you just include the method with some very basic code

#

it shouldn't be something too complex, just showcase the payload is changing

#

also i reccomend a shorter gap, not 10 entire seconds, make it like 2

#

since the user will most likely run this to understand, and feedback is important

#

Now we -> Now that we

#

but we still lack the main component -> ..., we still lack the main component

placid drum
#

okay

fervent lark
#

which is listeners -> which are listener (more specifically our TaskHooks)

#

code. we need -> code. We need

#

if there is no listeners ? -> if there are no listeners?

#

avoid so as a filler, sounds too casual

#

just follow it immediately ChronoGrapher handles this without any overhead by simply doing no-op.

#

again you re-state TaskHooks which was covered by the previous chapter

#

i wouldn't reintroduce to the user how to write TaskHooks, just write the TaskHook and appending code and saying before all that "For our case we will attach one listner (TaskHook) to monitor our notification task"

placid drum
#

okay

fervent lark
#

for

#[hooks({ OnNotificationsSent: NOTIFICATION_METRICS })]

You can avoid the LazyLock and simply use scoped singletons

#

discussed in the previous chapter

#

we have successfully implemented receiver on our custom event -> we have successfully implemented a receiver for our custom event

#

i am also noticing it sounds static-y, no smooth transitions

#

keep in mind this isn't API docs where you're free to even talk in a "robotic / lifeless" perspective (again, don't mean to offend there)

#

you have to tie the ideas in a cohesive narrative

#

its like writing a novel / a story. You don't say

Jack ran our machine and saw the gears turning, now jack moved to heating the engine

You say:

Jack ran our machine and saw the gears turning, jack then proceeded to heat the engine
#

mostly talking about the Hook-To-Hook communication pattern. This also saves you the explaining you would have to do (though doesn't seem like it). For example you could narrate it like so:

#
As shown in the terminal's outputs, we have successfully implemented receiver on our custom event. Now that we have discussed about custom event creation and how to emit them, a key question arises:

> "In which places we can emit our custom events for our receivers to listen?"

The answer is as long as you have access (even indirect through the context) to the Task, you can emit an event. This is where <Highlight color={"info"}>Hook-To-Hook Communication</Highlight> comes in picture.

In essence, its nothing more than emitting events in our TaskHooks, then other TaskHooks can listen to these events (and even emit their own events) which allows for a ripple effect to occur between TaskHooks.

This pattern is useful when TaskHooks aren't familar with the presence of other TaskHooks but may want to communicate with the unknown number of other TaskHooks by broadcasting information in a ping-pong fashion.
#

you can clearly identify there is cohesion, it ties the whole picture for the reader

#

the example ngl is a really good one

#

it shows one potential application of how TaskHooks would use, even if the code is "hypothetical"

#

for the last terminal output, add more sauce

#

elabrorate more

#

don't have 2 continiuous codeblocks (rarely you ever need to do that), break it up with paragraphs explaining the code

#

for bottom stuff, add a line via ---, then summarize the entire chapter and tease the user for the next chapter. Enrich the TL;DR

#

@placid drum i presume this feedback is extremely detailed, right?

placid drum
fervent lark
#

@placid drum you improved the PR?

#

i've noticed some other stuff

#

such as indentation

#[async_trait]
impl TaskHook<OnHighVolumeBurst> for AlertHook {
async fn on_event(&self, _ctx: &TaskHookContext, payload: &<OnHighVolumeBurst as TaskHookEvent>::Payload<'*>) {
    println!("ALERT: high-volume burst of {payload} notifications detected.");
}
}
#

impossible syntax '* -> '_

#

and quadruple backticks

placid drum
fervent lark
#

im the one sorry

#

my bad

placid drum
#

just did pr @fervent lark

#

also can you add what will be the next chapter about ?

fervent lark
#

we have a big problem with contribution rn

placid drum
#

is it slow ?

#

ya kinda

fervent lark
#

samy is leaving

#

honestly i can do so much, im also in the burnout phase

#

@placid drum do wish for the time being, for you to be consistent (not nescarilly perform the best but finish stuff, its better than sudden bursts) as these are kind of had times for the project

fervent lark
#

tbh il be busy, so i can't do much personally

#

critiques are something i can handle ofc, but when it comes to implementing stuff, thinking about the problems rn my time isn't particularily free

placid drum
#

i will give as much as time possible, but i cant commit to it 100% i have other things which need lot of attention for now.

fervent lark
#

prefer dots and commas, seems chatgpt-ish styled

placid drum
fervent lark
#

also As see output in terminal you may have notice few let me that to you, ????

#

it kinda doesn't make sense

#

though these stuff i presume i can polish them myself as well

#

again, the TL;DR is focusing on syntax

> **TL;DR.** Custom events are plain structs implementing `TaskHookEvent`, with an associated `Payload<'a>` for whatever data the event carries. Any Task can `emit` them, and any TaskHook can listen. Because hooks themselves can emit, you get Hook-To-Hook Communication which is truly powerfull.
#

avoid syntax mentions on generic paragraphs, use them only via RenderProgrammingLanguageBased block

placid drum
#

okay i will do that.

fervent lark
#

-# though i haven't established what the next chapter would be, i guess its a good time to think about it

#

btw how is the content of the guidebook plus the guidelines out of 10 (in depth) and why?

#

also i do start to realise setting a higher bar in terms of strictness would also be needed in the guidebook guidelines, its harder than API docs as its more narrative focused and varries on the topic

placid drum
placid drum
#

I would try to write cron macro. Just tell does it come in core or in different place?

fervent lark
fervent lark
#

merged

placid drum
#

so i am thinking starting with macro

fervent lark
#

ye

#

oh i think now i get what you mean

fervent lark
#

its one to one

#

as such make the cron macro adaptable as well with minimal changes, so i can directly embed its functionality there.

#

Or actually the more i think about it, what if we play it more clever

#

what if its just directly copy pastes whatever schedule contains, this way we both support external schedule types

#

BUT

#

for cron, intervals... etc:

#[task(schedule = cron!(...))]
async fn MyTask(...) -> Result<...> {...}
#

use the function-like macro version

#

since it copy pastes it, no extra functonality will need to be implemented

placid drum
#

okay, i'll look into it.

fervent lark
#

@placid drum

#

i published the polishing

#

actually wait

#

for some reason RustRover doesn't want to push the changes and is analysing the code

#

for an SVG

#

nvm

#

ok done, take a look on how it is

placid drum
fervent lark
#

For the task macro, im not sure if it would be useful but what if you could group methods?

#

like

#
pub struct MyTaskGroup {...}

impl MyTaskGroup {
   #[task(schedule = ...)]
   pub async fn MyTaskA(...) -> Result<...> {...}

   #[task(schedule = ...)]
   pub async fn MyTaskB(...) -> Result<...> {...}

   #[task(schedule = ...)]
   pub async fn MyTaskC(...) -> Result<...> {...}
}
#

imagine sort of "scoped globals"

placid drum
fervent lark
#

it could even inject it into the arguments automatically

placid drum
#

ya make sense

fervent lark
#

im gonna definitely have an issue with schedule

placid drum
#

why we dont have 50 hrs a day why??

fervent lark
placid drum
fervent lark
fervent lark
#

I finished the task macro

#
#[task(schedule = every!(1h))]
pub async fn MyAbcTask(ctx: &TaskFrameContext) -> Result<(), String> {
    todo!()
}
#

beautiful isn't it?

#

its also clever in that if it detects you have a task appended at the end of any capitalization and replaces it with Task

#

you can also configure it to be a singleton or multi-instance based

#

as well as inject more arguments. Though for Tasks like these, it errors out

#

you'd need a workfow to do that

#

also subsequently, the taskframe macro is done

#

that was quite quick ngl

#

it took me one afternoon to implement 2 proc-macros

#
#[taskframe]
pub async fn MyAbc2TaskFrame(_ctx: &TaskFrameContext) -> Result<(), String> {
    todo!()
}
#

i haven't added unit tests for rapid development reasons

#

also @formal sedge if you're thinking about unit tests, its gonna be a whole lot easier to test both at once over 2 seperate implementations

formal sedge
#

Oh

fervent lark
#

for taskframe do it typically, for task however just check the attribute params if they work, the return type when fetching or creating a Task instance and ig the frame type as well, and you're done

formal sedge
#

I totally forgot about the project😅

fervent lark
#

lol

formal sedge
#

How it’s going?

fervent lark
#

eh not so great

formal sedge
#

Lack of contributions ig?

fervent lark
#

ye

#

samy is taking a backseat due to heavy work he has

#

so im left with blackpearl as the only contributor

placid drum
#

macro's are beauty

#

didnt get to checkout hopefully i today get enough time to checkout

fervent lark
#

let me guess u didn't

placid drum
fervent lark
#

also didn't mean to offend you

placid drum
fervent lark
#

nvm then

placid drum
#

someone said to me this - if someone specially dosen't says to you, dont assume it because we only think from our perspective

#

i am becoming philosopher ah nooo

fervent lark
#

changed the 2 proc-macros

#
#[taskframe(name_override = MyCoolThing)]
pub async fn MyAbcTask(_ctx: &TaskFrameContext) -> Result<(), String> {
    todo!()
}

#[task(
    schedule = every!(1h),
    task_override = HelloWorld,
    taskframe_override = HelloWorld2,
)]
pub async fn MyAbcTask(_ctx: &TaskFrameContext) -> Result<(), String> {
    todo!()
}
#

Now you can do these stuff

fervent lark
#

Im also planning to support generics / lifetimes / const generics (with of course the where clauses)

fervent lark
#

ngl oh my

#

its so ergonomic

#

lifetimes sadly aren't really supported

#

due to 'static lifetime limitations

#

so only const generics and type generics

#

like writing this:

#

this thing:

#[taskframe(name_override = "MyCoolThing")]
pub async fn MyAbcTask<T: Send + Sync + 'static>(_ctx: &TaskFrameContext) -> Result<(), String> {
    todo!()
}

Litterally translates to:

pub struct MyCoolThing<T: Send + Sync + 'static> (PhantomData<( T )>);
impl<T: Send + Sync + 'static> Default for MyCoolThing<T> { 
    fn default() -> Self { Self(PhantomData) } 
}
impl<T: Send + Sync + 'static> Clone for MyCoolThing<T> { 
    fn clone(&self) -> Self { Self(PhantomData) } 
}
impl<T: Send + Sync + 'static> Copy for MyCoolThing<T> {}
impl<T: Send + Sync + 'static> TaskFrame for MyCoolThing<T> {
    type Args = ();
    type Error = String;
    async fn execute(&self, _ctx: &TaskFrameContext, args: &Self::Args) -> Result<(), Self::Error> {
        todo!()
    }
}
fervent lark
#

generics are tricky to get them to work with the task proc-macro

#

specifically singleton tasks

fervent lark
#

ngl it was suprisingly easier than i thought

#

it only took 3 days (and yesterday wasn't a productive day since i had irl stuff for most of the day)

#

the code's quality is questionable at best, due to rapid prototyping, i could try to clean it but im lazy rn

fervent lark
#
GitHub

High-Level Overview #[task(...)] Allows users to write their Tasks as an async function and have it translated directly to structs and trait implementations with support for unsafe, generics... etc...

GitHub

High-Level Overview #[taskframe(...)] Allows users to write their TaskFrame as an async function and have it translated directly to structs and trait implementations with support for unsafe, generi...

fervent lark
#

@placid drum i pesume you will be working on the cron! macro right?

#

I'm asking since i am planning to touch upon it and finish it, if you do however work on it then il try something else

fervent lark
#

okie good

#

afterward should i assign you something simpler or not?

fervent lark
#

mostly cleaning up the code, adding unit tests and API docs

placid drum
#

basically i am going through your changes

#

didnt not start on the cron yet

fervent lark
placid drum
fervent lark
#

ah

#

man its kinda crazy how this project has 1475 commits

#

a lot of work (though most like 40% are smaller changes but still)

placid drum
#

so i will start to work on cron

fervent lark
#

okie dokie

placid drum
#

we will do fast enough

fervent lark
#

its gonna save you headaches of "How do i make this prettier" when you implement the features

#

Make it right, fix the bloat and smells later

placid drum
#

okay

#

@fervent lark sorry for asking agian i have to get requirement clear about the cron

#

you can refer me to the preve convo also

fervent lark
#

hm?

fervent lark
#

basically

#

the cron macro hosts, well the cron syntax

#

cron!(* * * * * *)

#

like so

#

actually wait

fervent lark
#

@placid drum my bad ignoe the above

placid drum
#

okay

#

kk

fervent lark
#

you'll just have to make the cron!

#

thats all

#

utilizing the same stuff as the TaskScheduleCron without copy paste

#

for #[task(...)] macro, well it can be used directly so no need to care for it at all

placid drum
#

so i have to just improt the TaskScheduleCron

#

use proc_macro::TokenStream;
use quote::quote;
use syn::{LitStr, parse_macro_input};

pub fn cron(input: TokenStream) -> TokenStream {
    let parsed = parse_macro_input!(input as LitStr);
    TokenStream::from(quote! {
       chronographer::task::schedule::TaskScheduleCron::from_str(#parsed).unwrap()
    })
}
#

i saw the impl in every so ya

placid drum
#

okay

fervent lark
#

you aren't supposed to use the from_str method

#

rather

fervent lark
#

you are supposed to convert it to the CronFields

#

then pass them to the TaskScheduleCron

placid drum
#

oh now i under stand what you

fervent lark
#

while also erroring at compile-time whenever or not the cron syntax is valid

fervent lark
#

it just copy pastes it blindly

#

resulting in runtime errors which is something to avoid especially in the macro at all costs

#

if i wrote

#
cron!(Here is something that breaks it)

Then it would blindly paste it, not erroring me at compile-time

placid drum
#

can i just use cron parser from the base by improting ?

fervent lark
#

though not sure

#

i think i said something about the cron stuff

#

i don't want to expose the cron interpertation to the user

#

so you might need to package it as a seperate crate in the workspace

#

copy paste it there, include it both on the base API as well as the proc-macro API

#

and use it from there

#

one of the problems you will definitely encounter is the fact the lexer / parser is made for strings

#

and not for tokens, when it comes to errors, you will need to specify where that thing is you want to highlight with the error, with String you lose this information

placid drum
#

there will space b.w right just split and use it ig

placid drum
#

in the cron expression

fervent lark
#

you mean the cron expression will be split by space?

placid drum
#

ya

fervent lark
#

ye ofc

#

thats your only splitter

#

i reccomend looking the every! macro btw

#

you will notice it highlights errors exactly where they occur

placid drum
#

okay!

fervent lark
#

also update the project

#

since i pushed 1 hour ago some changes

placid drum
#

ya did

fervent lark
#

okie good

#

do remember though you need a "hidden" crate in the workspace

placid drum
#

okay.

fervent lark
#

if you plan to re-use the cron interperter (which is reccomended)

#

I do suggest for you to rework it in such a way

#

you can adapt it to the macro land

#

like don't just leave it as is

placid drum
#

okay

#

also about the new crate in workspace are you refering to create the cron and move the cron related stuff there ?

fervent lark
#

im referring specifically to move the internals of cron there

#

the CronParser, CronLexer and the cron translation

placid drum
#

so should make change in base to ?

fervent lark
#

on both it and the macros

placid drum
#

okay gotcha

#

i will question if i get stuck

fervent lark
#

okie dokie

#

for the translation layer of cron, well its embedded deep to TaskScheduleCron

#

so you will need to make it its own layer

#

but let TaskScheduleCron and the macro use it with modifications

#

I think i have an idea

#

on how you could unify these two in one

placid drum
#

go ahead listening.

fervent lark
#

let the lexer translate the rust expression and strings into an IR (Intermediate Representation)

#

this IR hosts both span information (if included), and everything common to both strings and macros

#

then you can work with the IR. When its time to throw errors, throw them with all the information

#

in the macros, you will extract these and translate it over to a syn::Error compile-time error

#

in strings you ignore most stuff

#

i think this should save you more time than if you were to manually remake the cron interperter

#

plus save me from tech debt

placid drum
#

okay sounds like an idea.

#

also should i proc-macro2 = "1.0.101" in wroksapce and use in /proc and /cron like it needed to space

placid drum
# fervent lark what?

i am saying is that should i add pro-macro2 in workspace i needed in the new cron crate also need this error

fervent lark
#

also i've pushed more changes

fervent lark
placid drum
#

okay i'll add in cron crate

fervent lark
#

Rust requires proc_macro to be true

fervent lark
placid drum
#

okay

fervent lark
#

added API docs for every! function-like macro as well as task and taskframe attribute macros

#

i kind of strayed away from the API doc guidelines, i see a couple of issues with it, it assumes a particular shape of macros and isn't explicit

#

i plan to update them to reflect more explicitness and allow for broader macro shapes (while retaining strictness on defining them)

fervent lark
#

also i do want to make the macros more powerful

fervent lark
#

one idea for both is the template macro

#

it modifies the constructor to require arguments up front, though requires non_singleton for the Task

#

then these constructor parameters can be used inside the function

#
#[taskframe]
#[template(data1: u8, data2: Vec<u8>, data3: String)]
pub async fn MyTask(_ctx: &TaskFrameContext, arg1: u8, arg2: String) -> Result<(), ...> {
   println!("{data1:?}"); // Using constructor parameters
}
#

other ideas include grouping Tasks with a struct or enum allowing them to share state with each other:

#[derive(TaskGroup)]
struct MyTaskState {
   data1: u8,
   data2: Vec<u8>,
   data3: String
}

impl MyTaskState {
   #[task(schedule = every!(1s))]
   pub async fn MyFirstTask(&self, _ctx: &TaskFrameContext, ...) -> Result<(), [ERROR]> {
       todo!();
   }

   #[task(schedule = every!(2s))]
   pub async fn MySecondTask(&self, _ctx: &TaskFrameContext, ...) -> Result<(), [ERROR]> {
       todo!();
   }

   #[task(schedule = every!(3s))]
   pub async fn MyThirdTask(&self, _ctx: &TaskFrameContext, ...) -> Result<(), [ERROR]> {
       todo!();
   }
}
#

the entire struct is shared all at once with each Task inside the impl

#

additionally the macro attaches more methods for working with the entire group at once, such as scheduling all of them simultaneously

#

but both are just ideas not yet formalized, these will definitely not be included with the first version of ChronoGrapher as they are too complex to make for now

fervent lark
#

Ladies and gents

#[chronographer::main]
async fn main(scheduler1: DefaultLiveScheduler<Box<dyn TaskError>>) {
    todo!()
}
#

the main macro is finished

#

you can inject any number of arguments as long as they are schedulers, they will be automatically initialized and started

#

you can return a Result<T, E> type and use ? in the code just like the main function via thread_count

#

you can specify the thread count and even the ordering for starting the schedulers (whenever or not to start before or after the user code)

#

honestly we are getting closer and closer to the first release which is good

placid drum
#

good progress i am stuck at the cron level

fervent lark
#

lol

fervent lark
#

ngl this summer will require a lot of progress, we've still got outside stuff to handle apart from coding the core library

placid drum
#

no question just wtf i am doing and why like

okey so
my plan is to take out cron lexer keep it in utils where i created the
fn tokeniz from token

fervent lark
#

such as the CI/CD pipeline, the Guidebook Docs, the Website

placid drum
#

but the problem i am not able to visualize the flow to see oh ya this fn should do this and that is this work

#

its more like i am hitting the blank wall

#

did i explian correctly ?

#

ahh i dont know how to explain better, srry about that.

#

somethinng like this to have interface we can directly work with the stream

#

is it correct direction ?

fervent lark
#

i haven't viewed the entire code

#

but whatever the case, your job is to convert it to an IR of tokens that being adaptable for both macros and String use case as well

placid drum
#

okay

fervent lark
#

when you get an error in the cron expression, always include additional information like span so the macro can report correctly

placid drum
#

how can i test it ??

#

wirte test ?

fervent lark
placid drum
#

nope not entire

fervent lark
#

ah

placid drum
#

wroking on parser

fervent lark
#

for tests when it comes to the components inside the utils crate

#

im not sure...

#

for macros and stuff look in the tests/ folder, there is ui/ and src/macros

placid drum
#

okay

fervent lark
#

added API docs for the main macro

placid drum
#

@fervent lark added pr for the cron but didnt change in the base

#

i though take your approval first then change

fervent lark
#

ok

fervent lark
#

i reccomend tokenize_from_tokens should live in proc purely

#

and the other tokenize_from_str can live there

#

also you convert to string always

#

like this is an identifier:

 let token_type = match s.to_uppercase().as_str() {
                    "L" => TokenType::Last,
                    "W" => TokenType::NearestWeekday,
                    "SUN" => TokenType::Value(1),
                    "MON" => TokenType::Value(2),
                    "TUE" => TokenType::Value(3),
                    "WED" => TokenType::Value(4),
                    "THU" => TokenType::Value(5),
                    "FRI" => TokenType::Value(6),
                    "SAT" => TokenType::Value(7),
                    "JAN" => TokenType::Value(1),
                    "FEB" => TokenType::Value(2),
                    "MAR" => TokenType::Value(3),
                    "APR" => TokenType::Value(4),
                    "MAY" => TokenType::Value(5),
                    "JUN" => TokenType::Value(6),
                    "JUL" => TokenType::Value(7),
                    "AUG" => TokenType::Value(8),
                    "SEP" => TokenType::Value(9),
                    "OCT" => TokenType::Value(10),
                    "NOV" => TokenType::Value(11),
                    "DEC" => TokenType::Value(12),
                    _ => return Err((CronLexerError::UnknownCharacter, ident.span())),
                };
#

i also notice you uppercase which you assume the shape is either all lowercase or all uppercase

#

what prevents me doing? MoN

#

apart from those innacurarcy, it seems relatively good

#

while i saw spans in the macro version for errors which is always good, i presume you handle them correctly, not lazily random spans or too general ones?

#

@placid drum do i merge it or are you planning to modify more stuff?

placid drum
placid drum
#

right now we have more code duplicated so yah.

fervent lark
#

ok

fervent lark
placid drum
#

okay

fervent lark
#

also once you finish the cron stuff

#

you might want to tackle the unit tests for the rest 3 macros

#

or smth like that, i think i've already said what you would do next, don't remember

#

also il not be availaible much as irl stuff kicks in

fervent lark
#

okie

fervent lark
#

not sure though

placid drum
#

no worry

fervent lark
#

also used darling to make the code more pretty and mantainable

#

@placid drum if you are wondering what is darling and if you might use it, well, the only time you will need to use it is for attribute macros (maybe derive macros) and specifically ones with attribute parameters. You will probably never need to touch it in your life

placid drum
#

ig i would be lot busy today

fervent lark
#

the good news is summer is approaching

#

so once i finish the irl stuff, il have basically unlimited time

#

the problem will be we will need contribution help

#

this summer period is quite important for the project, since everyone is free and its Septemember is approaching, we must take full advantage of it

#

and get the core shipped

placid drum
#

did not got leaves

fervent lark
#

@placid drum ?

placid drum
#

yes

fervent lark
placid drum
fervent lark
#

am i stupid or smth lol

placid drum
placid drum
fervent lark
#

the workflow macro is gonna be complex 💀

#

everything i tackled was a toy compared to this beast

placid drum
fervent lark
#

i would prefer any day writing:

#
#[task(schedule = every!(2s)]
#[workflow(
  timeout(5s)
  dependency(MyDependency1 || MyDependency2),
  fallback(my_taskframe1, my_taskframe2),
  retry(5, 2s)
)]
async fn BackupSystem(ctx: &TaskFrameContext) -> Result<(), String> {
   // ...
}
#

than:

pub struct BackupSystemTask;
impl BackupSystemTask { 
   pub fn new() -> Task<
        TimeoutTaskFrame<
            DependencyTaskFrame<
                FallbackTaskFrame<
                    FallbackTaskFrame<
                      RetriableTaskFrame<BackupSystemTaskFrame>
                    >,
                    [my_taskframe2 TYPE]
                >,
                [my_taskframe2 TYPE]
            >
        >
   > { 
      Task::new(
         TimeoutTaskFrame::new(
              DependencyTaskFrame::new(
                  FallbackTaskFrame::new(
                      FallbackTaskFrame::new(
                          RetriableTaskFrame::new(
                              5, Duration::from_secs(2), 
                              MyTaskFrameTaskFrame::default()
                          ),
                          my_taskframe1
                      ),
                      my_taskframe2
                  ),
                  MyDependency1 | MyDependency2
              )
              Duration::from_secs(5)
         ),
         every!(2s)
      ) 
   } 
}

#[derive(Default, Clone, Copy)]
pub struct BackupSystemTaskFrame;
impl TaskFrame for BackupSystemTaskFrame {
    type Args = ();
    type Error = String;

    async fn execute(&self, ctx: &TaskFrameContext, args: &Self::Args) -> Result<(), Self::Error> {
        // ...
    }
}
#

this is like 100x more mantainable (the macros stuff)

#

and this is like "basic" workflows

#

well not truly simple retries and that, but its typical stuff

#

like this is kind of expected for something serious

#

you would need to configure a timeout to prevent tasks from hanging, fallbacks for error handling and retries as a first "weak" layer of defense if its an API request or I/O stuff which can fail but can be resolved by simply retrying

#

hooks compound this further, if you are more serious about metrics, logging and alerts. You would need to attach various TaskHooks onto the Task

fervent lark
#

i haven't included singleton stuff

#

imagine with generics as well, reordering the workflow, changing the arguments of the backup TaskFrame

#

you'd have to change a monlith

#

imagine making the same Task types with a couple of changes over and over again

fervent lark
#

same with delay and timeout

placid drum
#

Damn you are fast

fervent lark
placid drum
#

okay but still

fervent lark
#

i've also finished fallback

fervent lark
#

instead of copy-paste code everywhere and cleaning it afterwards

placid drum
#

nice

fervent lark
#

Its better to have stuff like this

pub struct RetryArguments {
    max: syn::Expr,
    delay: Option<RetryDelay>,
    when: Option<RetryErrorFilter>
}

impl Parse for RetryArguments {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let mut argument_parser = ArgumentParser::new(input);
        let max = argument_parser.parse_required::<syn::Expr>("max")?;
        let delay = argument_parser.parse_optional::<RetryDelay>("delay")?;
        let when = argument_parser.parse_optional::<RetryErrorFilter>("when")?;
        Ok(RetryArguments { max, delay, when })
    }
}
#

an argument parser that does the parsing for me

#

i just specify the structure to parse as, and it automatically does it for me

#

handling commas and so on

#

you should try to adopt this mindset of simplifying as much as possible your life for making features faster

#

you could finish cron! way quicker if you think about the path to least resistance (without bloat)

fervent lark
#

i've also made the parsing for retries and so on more strict

#

it doesn't accept just anything, max is required to be LitInt or either a closure, an ident or macro

#

finshed also threshold

#

tbh if we ever want more workflow primitives in the macros for a new TaskFrame, we can simply add a new one

#

its really simple

#

sure some definitions repeat, its not 10/10 best but who cares

#

its more mantainable than other patterns

placid drum
#

sometime DRY dosnt work

fervent lark
#

if it doesn't work, you usually are the problem

#

the abstraction you set prevents you

placid drum
#

more abstraction create more problem which can be solve if its not abstracted (deep enough)

fervent lark
#

was more talking about the approach

#

you take to solving the problem

#

also i finished condition. The last stuff are dependency and inject

#

inject just takes a closure of the TaskFrame and dynamically injects extra TaskFrames to it

placid drum
#

but i feel both are fine but depends how often it get change

fervent lark
#

the dependency workflow primitive is done

#

everything except inject is done basically

fervent lark
#

one hard part is gonna be figuring out how I can transform my AST into code

#

utilizing ChronoGrapher's stuff

fervent lark
#

wait

fervent lark
#

i've "sort" of finished the translation of the retry workflow primitive

#

the only thing left is translating the error filter into match arms

fervent lark
#

finsihed the retry macro's translations

#

ngl holy smokes even with just one retry, its ergonomic

#

it produces this closure

|x: MyTaskFrame| {        
   RetriableTaskFrame::builder()
       .frame(x)
       .retries(NonZeroU32::new(1).unwrap())
       .when(|err| { !matches!(err , Some (0..=10 | 50 | _)) })
       .build()
};
#

yes ik i have used integers, but thats for testing

#

all this from:

#[workflow(
    retry(1, 2s, when = [0..=10, 50, _])
)]
#

-# Discord's syntax highlighter acting wonky

#

Give or take, i had to do some voodo magic to get there

#

and still have to

#

i have to parse this thing into my own AST representation, then once done, translate it back to tokens specifically the Base API equivalent

#

the closure is also done on purpose so its independent from the #[task(...)] or #[taskframe(...)] macro

#

-# (I do feel as if we are gonna have a problem with singletons in the workflow macro, yuck....)

fervent lark
#

Its beautiful

#
|x: MyTaskFrame| {
    FallbackTaskFrame::new(
        FallbackTaskFrame::new(
            FallbackTaskFrame::new(
                RetriableTaskFrame::builder()
                    .frame(DelayTaskFrame::new(
                        TimeoutTaskFrame::new(
                            x,
                            Duration::from_secs_f64(72000f64),
                        ),
                        Duration::from_secs_f64(43200000f64),
                    ))
                    .retries(NonZeroU32::new(1).unwrap())
                    .when(|err| matches!(err , Some (0..=10 | 50 | _)))
                    .build(),
                MyTaskFrame1,
            ),
            MyTaskFrame2,
        ),
        MyTaskFrame3,
    )
};
#

all from this

#[workflow(
    timeout(20s),
    delay(500ms)
    retry(1, 2s, when = [0..=10, 50, _]),
    fallback(MyTaskFrame1, MyTaskFrame2, MyTaskFrame3)
)]
#

i think proc macros really cool ngl (with a slight asterisk on some stuff), i think i found a new addiction lol

fervent lark
#

dependency is also finished

placid drum
#

@fervent lark getting error cron - Unexpected token sequence found everything is correct building correctly but dont know somehow its giving me error.

fervent lark
#

what did you do exactly?

#

show the full error

placid drum
#

its in my pr btw but i am not able to figureout why its giving me this

#

there is no build failing

#

also its come from rust analyzer

fervent lark
#

can't help tbh if i don't have the full thing

placid drum
#

it is the error come when i use on
let schedule = cron!(* * * * * *);

on first * Unexpected token sequence found i get this

fervent lark
#

¯_(ツ)_/¯

#

dunno really what could be causing this

placid drum
#

i'll figureout somthing

fervent lark
#

i have the suspicion its a .parse() method failing

#

when you're translating the tokens into your own stuff

placid drum
#

okay i'll check that out

fervent lark
placid drum
fervent lark
#

ah

placid drum
#

@fervent lark solve the issue but did not replace with base

fervent lark
#

wdym did not replace with base

placid drum
#

replace with already exits cron in base ?

fervent lark
#

ok

#

ngl there will be quite the code smell

#

brace yourself

fervent lark
#

its gonna require some funky stuff

#

like looking specifically for the workflow attribute, then taking it, along with all of its parameters, parsing and translating it from within the macro (task or taskframe) and then modifying

fervent lark
fervent lark
#

for the workflow macro one of the ideas is to have an internal parameter lets call it __internal_workflow_spec inside the #[taskframe] macro

#

for the task macro, we would take the everything the workflow has and copy paste it in the parameter no questions asked

#

then taskframe could parse it on its own

#

for the taskframe macro though, there are two techniques, either make an internal macro to initialize taskframes or

#

make the taskframe auto-detect the workflow, extract the tokens and parse it

#

most likely the second choice is easier

fervent lark
#

the code is atrocious

fervent lark
#

updated btw the docs

#

Imma make an experimental branch

deep mesa
placid drum
#

damn i run cargo fmt

#

in workspace its disaster

#

@fervent lark

#

you can merge pr its done from my side

#

its took while

placid drum
#

if it possible you can review it

fervent lark
fervent lark
fervent lark
#

ok seems good enough to merge

#

one thing @placid drum, you should add API docs as well for the cron! macro

fervent lark
#

man... This is gonna be pain in the complete ass

fervent lark
#

the workflow stuff i mean ofc

fervent lark
#

apparently i've realized there is a "slight" limitation

#

in the workflow macro

#

specifically everything that uses a TaskFrame, for example in fallback and condition

#

there isn't a way for me to distinguish between using the macro to create a TaskFrame or manual implementation

#

one solution i have is by default all TaskFrames use the default method

#

however... If you want to use the workflow of another TaskFrame, you can create a TaskFrame instance

#

i could allow this

#

or

#

another way is to use a special symbol to indicate TaskFrames that want to only use their code and not the workflow itself

#

idrk

fervent lark
#

Sadly... There is a real limitation

#

in the workflow macro

#

fallback(...) cannot inherit workflows from its fallbacks

#

More specifically:

#[taskframe]
pub async fn MyTaskFrame1(ctx: &TaskFrameContext, error: TimeoutTaskFrameError<String>) -> Result<(), String> {
    Ok(())
}


#[taskframe]
#[workflow(
    retry(3),
    timeout(5s),
    fallback(MyTaskFrame1)
)]
pub async fn MyTaskFrame(ctx: &TaskFrameContext) -> Result<(), String> {
    todo!()
}

The above works like a charm, but below doesn't:

#[taskframe]
#[workflow(...)]
pub async fn MyTaskFrame1(ctx: &TaskFrameContext, error: TimeoutTaskFrameError<String>) -> Result<(), String> {
    Ok(())
}


#[taskframe]
#[workflow(
    retry(3),
    timeout(5s),
    fallback(MyTaskFrame1)
)]
pub async fn MyTaskFrame(ctx: &TaskFrameContext) -> Result<(), String> {
    todo!()
}

You only copy raw code from MyTaskFrame1 and not the entire workflow

#

advanced fallbacks will need to deal with the base API (if each fallback has its own TaskFrames), or when i get to work the inject(...) primitive

#

i really hate this limitation tbh

#

i despise it basically

#

i added an issue just for this in case anyone complains about it

fervent lark
#

the only problem btw is now you can't know the concrete type of the workflow

#

its just impl TaskFrame

#

but at least workflow inheritance is possible

#

one of my absolute favourite things is generic workflows

#
#[taskframe]
#[workflow(
    retry(3),
    fallback(TF::default())
)]
pub async fn MyTaskFrame<TF>(ctx: &TaskFrameContext) -> Result<(), String>
where
    TF: TaskFrame<Args = String, Error = String> + Default
{
    todo!()
}
#

to my brain its really cool to be able to configure the workflow primitives with generics

#

also one issue i've noticed is when using timeout or anything that can cause an error on its own

#

well... you'd have to manually type in the return Result<T, E> the TimeoutTaskFrameError<E>

#

as an error parameter for say a fallback its ok

#

one idea i have is for the macro to automatically do it for you and map it to the corresponding error

fervent lark
#

the way i'd do this might involve having a workflow! macro

#

yes there will be two macros for attribute and function-like

#

the idea of the function-like version is for users to be able to use it in scenarios where using the attribute version doesn't suffice

fervent lark
#

also this is kinda annoying:

#

@placid drum btw

#

for the next task i don't have anything in mind specifically, except unit tests but you aren't as interested to tackle them ig

#

I will do API docs for the cron! macro

placid drum
#

i was thinking same why not finish cron! api doc

fervent lark
#

the workflow macro has one WIP area, specifically condition but its kind of a mind fuck for you to solve (same as me)

fervent lark
#

ok then do it ig

placid drum
#

okay

fervent lark
#

constants like MON aren't positional

#

meaning you can apply MON to year and its fine

#

hm we've got a problem...

#

the task macro is forced to be in the shape of Task<impl TaskFrame>, everything is basically erased